Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
STIXReportModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.modules.stix;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.List;
26 import java.util.logging.Level;
27 import java.io.BufferedWriter;
28 import java.io.FileWriter;
29 import java.util.Arrays;
30 import javax.swing.JPanel;
31 import javax.xml.bind.JAXBContext;
32 import javax.xml.bind.JAXBException;
33 import javax.xml.bind.Unmarshaller;
34 import javax.xml.namespace.QName;
35 
36 import org.mitre.cybox.cybox_2.ObjectType;
37 import org.mitre.cybox.cybox_2.Observable;
38 import org.mitre.cybox.cybox_2.ObservableCompositionType;
39 import org.mitre.stix.common_1.IndicatorBaseType;
40 import org.mitre.stix.indicator_2.Indicator;
41 import org.mitre.stix.stix_1.STIXPackage;
42 
45 import org.openide.util.NbBundle;
47 import org.sleuthkit.datamodel.TskCoreException;
48 
49 import org.mitre.cybox.cybox_2.OperatorTypeEnum;
50 import org.mitre.cybox.objects.Address;
51 import org.mitre.cybox.objects.FileObjectType;
52 import org.mitre.cybox.objects.URIObjectType;
53 import org.mitre.cybox.objects.EmailMessage;
54 import org.mitre.cybox.objects.WindowsNetworkShare;
55 import org.mitre.cybox.objects.AccountObjectType;
56 import org.mitre.cybox.objects.SystemObjectType;
57 import org.mitre.cybox.objects.URLHistory;
58 import org.mitre.cybox.objects.DomainName;
59 import org.mitre.cybox.objects.WindowsRegistryKey;
63 
67 public class STIXReportModule implements GeneralReportModule {
68 
69  private static final Logger logger = Logger.getLogger(STIXReportModule.class.getName());
71  private static STIXReportModule instance = null;
72  private String reportPath;
73  private boolean reportAllResults;
74 
75  private Map<String, ObjectType> idToObjectMap = new HashMap<String, ObjectType>();
76  private Map<String, ObservableResult> idToResult = new HashMap<String, ObservableResult>();
77 
78  private List<EvalRegistryObj.RegistryFileInfo> registryFileData = null;
79 
80  private final boolean skipShortCircuit = true;
81 
82  private BufferedWriter output = null;
83 
84  // Hidden constructor for the report
85  private STIXReportModule() {
86  }
87 
88  // Get the default implementation of this report
89  public static synchronized STIXReportModule getDefault() {
90  if (instance == null) {
91  instance = new STIXReportModule();
92  }
93  return instance;
94  }
95 
102  @Override
103  public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) {
104  // Start the progress bar and setup the report
105  progressPanel.setIndeterminate(false);
106  progressPanel.start();
107  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX"));
108  reportPath = baseReportDir + getRelativeFilePath();
109 
110  // Check if the user wants to display all output or just hits
111  reportAllResults = configPanel.getShowAllResults();
112 
113  // Set up the output file
114  try {
115  File file = new File(reportPath);
116  output = new BufferedWriter(new FileWriter(file));
117  } catch (IOException ex) {
118  logger.log(Level.SEVERE, String.format("Unable to open STIX report file %s", reportPath), ex); //NON-NLS
119  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
120  NbBundle.getMessage(this.getClass(),
121  "STIXReportModule.notifyMsg.unableToOpenReportFile",
122  reportPath),
124  progressPanel.complete(ReportStatus.ERROR);
125  progressPanel.updateStatusLabel(
126  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
127  return;
128  }
129 
130  // Keep track of whether any errors occur during processing
131  boolean hadErrors = false;
132 
133  // Process the file/directory name entry
134  String stixFileName = configPanel.getStixFile();
135  if (stixFileName == null) {
136  logger.log(Level.SEVERE, "STIXReportModuleConfigPanel.stixFile not initialized "); //NON-NLS
138  NbBundle.getMessage(this.getClass(), "STIXReportModule.notifyErr.noFildDirProvided"));
139  progressPanel.complete(ReportStatus.ERROR);
140  progressPanel.updateStatusLabel(
141  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
142  return;
143  }
144  if (stixFileName.isEmpty()) {
145  logger.log(Level.SEVERE, "No STIX file/directory provided "); //NON-NLS
147  NbBundle.getMessage(this.getClass(), "STIXReportModule.notifyErr.noFildDirProvided"));
148  progressPanel.complete(ReportStatus.ERROR);
149  progressPanel.updateStatusLabel(
150  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
151  return;
152  }
153  File stixFile = new File(stixFileName);
154 
155  if (!stixFile.exists()) {
156  logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName)); //NON-NLS
157  MessageNotifyUtil.Message.error(NbBundle.getMessage(this.getClass(),
158  "STIXReportModule.notifyMsg.unableToOpenFileDir",
159  stixFileName));
160  progressPanel.complete(ReportStatus.ERROR);
161  progressPanel.updateStatusLabel(
162  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName));
163  return;
164  }
165 
166  // Store the path
167  ModuleSettings.setConfigSetting("STIX", "defaultPath", stixFileName); //NON-NLS
168 
169  // Create array of stix file(s)
170  File[] stixFiles;
171  if (stixFile.isFile()) {
172  stixFiles = new File[1];
173  stixFiles[0] = stixFile;
174  } else {
175  stixFiles = stixFile.listFiles();
176  }
177 
178  // Set the length of the progress bar - we increment twice for each file
179  progressPanel.setMaximumProgress(stixFiles.length * 2 + 1);
180 
181  // Process each STIX file
182  for (File file : stixFiles) {
183  try {
184  processFile(file.getAbsolutePath(), progressPanel);
185  } catch (TskCoreException ex) {
186  logger.log(Level.SEVERE, String.format("Unable to process STIX file %s", file), ex); //NON-NLS
187  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
188  ex.getLocalizedMessage(),
190  hadErrors = true;
191  }
192 
193  // Clear out the ID maps before loading the next file
194  idToObjectMap = new HashMap<String, ObjectType>();
195  idToResult = new HashMap<String, ObservableResult>();
196  }
197 
198  // Close the output file
199  if (output != null) {
200  try {
201  output.close();
202  } catch (IOException ex) {
203  logger.log(Level.SEVERE, String.format("Error closing STIX report file %s", reportPath), ex); //NON-NLS
204  }
205  }
206 
207  // Set the progress bar to done. If any errors occurred along the way, modify
208  // the "complete" message to indicate this.
209  if (hadErrors) {
210  progressPanel.complete(ReportStatus.ERROR);
211  progressPanel.updateStatusLabel(
212  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
213  } else {
214  progressPanel.complete(ReportStatus.COMPLETE);
215  }
216  }
217 
226  private void processFile(String stixFile, ReportProgressPanel progressPanel) throws
227  TskCoreException {
228 
229  // Load the STIX file
230  STIXPackage stix;
231  stix = loadSTIXFile(stixFile);
232 
233  printFileHeader(stixFile);
234 
235  // Save any observables listed up front
236  processObservables(stix);
237  progressPanel.increment();
238 
239  // Make copies of the registry files
240  registryFileData = EvalRegistryObj.copyRegistryFiles();
241 
242  // Process the indicators
243  processIndicators(stix);
244  progressPanel.increment();
245 
246  }
247 
257  private STIXPackage loadSTIXFile(String stixFileName) throws TskCoreException {
258  try {
259  // Create STIXPackage object from xml.
260  File file = new File(stixFileName);
261  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
262  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
263  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
264  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
265  return stix;
266  } catch (JAXBException ex) {
267  logger.log(Level.SEVERE, String.format("Unable to load STIX file %s", stixFileName), ex.getLocalizedMessage()); //NON-NLS
268  throw new TskCoreException("Error loading STIX file (" + ex.toString() + ")"); //NON-NLS
269  }
270  }
271 
278  private void processObservables(STIXPackage stix) {
279  if (stix.getObservables() != null) {
280  List<Observable> obs = stix.getObservables().getObservables();
281  for (Observable o : obs) {
282  if (o.getId() != null) {
283  saveToObjectMap(o);
284  }
285  }
286  }
287  }
288 
295  private void processIndicators(STIXPackage stix) throws TskCoreException {
296  if (stix.getIndicators() != null) {
297  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
298  for (IndicatorBaseType t : s) {
299  if (t instanceof Indicator) {
300  Indicator ind = (Indicator) t;
301  if (ind.getObservable() != null) {
302  if (ind.getObservable().getObject() != null) {
303  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
304  if (result.isTrue() || reportAllResults) {
305  writeResultsToFile(ind, result.getDescription(), result.isTrue());
306  }
307  if (result.isTrue()) {
308  saveResultsAsArtifacts(ind, result);
309  }
310  } else if (ind.getObservable().getObservableComposition() != null) {
311  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
312 
313  if (result.isTrue() || reportAllResults) {
314  writeResultsToFile(ind, result.getDescription(), result.isTrue());
315  }
316  if (result.isTrue()) {
317  saveResultsAsArtifacts(ind, result);
318  }
319  }
320  }
321  }
322  }
323  }
324  }
325 
334  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result) throws TskCoreException {
335 
336  if (result.getArtifacts() == null) {
337  return;
338  }
339 
340  // Count of how many artifacts have been created for this indicator.
341  int count = 0;
342 
343  for (StixArtifactData s : result.getArtifacts()) {
344 
345  // Figure out what name to use for this indicator. If it has a title,
346  // use that. Otherwise use the ID. If both are missing, use a
347  // generic heading.
348  if (ind.getTitle() != null) {
349  s.createArtifact(ind.getTitle());
350  } else if (ind.getId() != null) {
351  s.createArtifact(ind.getId().toString());
352  } else {
353  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
354  }
355 
356  // Trying to protect against the case where we end up with tons of artifacts
357  // for a single observable because the condition was not restrictive enough
358  count++;
359  if (count > 1000) {
360  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
361  NbBundle.getMessage(this.getClass(),
362  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000",
363  ind.getId()),
365  break;
366  }
367  }
368 
369  }
370 
379  private void writeResultsToFile(Indicator ind, String resultStr, boolean found) {
380  if (output != null) {
381  try {
382  if (found) {
383  output.write("----------------\r\n"
384  + "Found indicator:\r\n"); //NON-NLS
385  } else {
386  output.write("-----------------------\r\n"
387  + "Did not find indicator:\r\n"); //NON-NLS
388  }
389  if (ind.getTitle() != null) {
390  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
391  } else {
392  output.write("\r\n");
393  }
394  if (ind.getId() != null) {
395  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
396  }
397 
398  if (ind.getDescription() != null) {
399  String desc = ind.getDescription().getValue();
400  desc = desc.trim();
401  output.write("Description: " + desc + "\r\n"); //NON-NLS
402  }
403  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
404  } catch (IOException ex) {
405  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
406  }
407  }
408  }
409 
415  private void printFileHeader(String a_fileName) {
416  if (output != null) {
417  try {
418  char[] chars = new char[a_fileName.length() + 8];
419  Arrays.fill(chars, '#');
420  String header = new String(chars);
421  output.write("\r\n" + header);
422  output.write("\r\n");
423  output.write("### " + a_fileName + " ###\r\n");
424  output.write(header + "\r\n\r\n");
425  } catch (IOException ex) {
426  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
427  }
428 
429  }
430 
431  }
432 
440  private String makeMapKey(Observable obs) {
441  QName idQ;
442  if (obs.getId() != null) {
443  idQ = obs.getId();
444  } else if (obs.getIdref() != null) {
445  idQ = obs.getIdref();
446  } else {
447  return "";
448  }
449 
450  return idQ.getLocalPart();
451  }
452 
458  private void saveToObjectMap(Observable obs) {
459 
460  if (obs.getObject() != null) {
461  idToObjectMap.put(makeMapKey(obs), obs.getObject());
462  }
463  }
464 
475  private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
476  if (comp.getOperator() == null) {
477  throw new TskCoreException("No operator found in composition"); //NON-NLS
478  }
479 
480  if (comp.getObservables() != null) {
481  List<Observable> obsList = comp.getObservables();
482 
483  // Split based on the type of composition (AND vs OR)
484  if (comp.getOperator() == OperatorTypeEnum.AND) {
485  ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
486  for (Observable o : obsList) {
487 
488  ObservableResult newResult; // The combined result for the composition
489  if (o.getObservableComposition() != null) {
490  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
491  if (result == null) {
492  result = newResult;
493  } else {
494  result.addResult(newResult, OperatorTypeEnum.AND);
495  }
496  } else {
497  newResult = evaluateSingleObservable(o, spacing + " ");
498  if (result == null) {
499  result = newResult;
500  } else {
501  result.addResult(newResult, OperatorTypeEnum.AND);
502  }
503  }
504 
505  if ((!skipShortCircuit) && !result.isFalse()) {
506  // For testing purposes (and maybe in general), may not want to short-circuit
507  return result;
508  }
509  }
510  // At this point, all comparisions should have been true (or indeterminate)
511  if (result == null) {
512  // This really shouldn't happen, but if we have an empty composition,
513  // indeterminate seems like a reasonable result
514  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
515  }
516 
517  return result;
518 
519  } else {
520  ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
521  for (Observable o : obsList) {
522 
523  ObservableResult newResult;// The combined result for the composition
524 
525  if (o.getObservableComposition() != null) {
526  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
527  if (result == null) {
528  result = newResult;
529  } else {
530  result.addResult(newResult, OperatorTypeEnum.OR);
531  }
532  } else {
533  newResult = evaluateSingleObservable(o, spacing + " ");
534  if (result == null) {
535  result = newResult;
536  } else {
537  result.addResult(newResult, OperatorTypeEnum.OR);
538  }
539  }
540 
541  if ((!skipShortCircuit) && result.isTrue()) {
542  // For testing (and maybe in general), may not want to short-circuit
543  return result;
544  }
545  }
546  // At this point, all comparisions were false (or indeterminate)
547  if (result == null) {
548  // This really shouldn't happen, but if we have an empty composition,
549  // indeterminate seems like a reasonable result
550  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
551  }
552 
553  return result;
554  }
555  } else {
556  throw new TskCoreException("No observables found in list"); //NON-NLS
557  }
558  }
559 
571  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
572 
573  // If we've already calculated this one, return the saved value
574  if (idToResult.containsKey(makeMapKey(obs))) {
575  return idToResult.get(makeMapKey(obs));
576  }
577 
578  if (obs.getIdref() == null) {
579 
580  // We should have the object data right here (as opposed to elsewhere in the STIX file).
581  // Save it to the map.
582  if (obs.getId() != null) {
583  saveToObjectMap(obs);
584  }
585 
586  if (obs.getObject() != null) {
587 
588  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
589  idToResult.put(makeMapKey(obs), result);
590  return result;
591  }
592  }
593 
594  if (idToObjectMap.containsKey(makeMapKey(obs))) {
595  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
596  idToResult.put(makeMapKey(obs), result);
597  return result;
598  }
599 
600  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
601  }
602 
612  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
613 
614  EvaluatableObject evalObj;
615 
616  if (obj.getProperties() instanceof FileObjectType) {
617  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
618  } else if (obj.getProperties() instanceof Address) {
619  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
620  } else if (obj.getProperties() instanceof URIObjectType) {
621  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
622  } else if (obj.getProperties() instanceof EmailMessage) {
623  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
624  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
625  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
626  } else if (obj.getProperties() instanceof AccountObjectType) {
627  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
628  } else if (obj.getProperties() instanceof SystemObjectType) {
629  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
630  } else if (obj.getProperties() instanceof URLHistory) {
631  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
632  } else if (obj.getProperties() instanceof DomainName) {
633  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
634  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
635  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
636  } else {
637  // Try to get the object type as a string
638  String type = obj.getProperties().toString();
639  type = type.substring(0, type.indexOf("@"));
640  if ((type.lastIndexOf(".") + 1) < type.length()) {
641  type = type.substring(type.lastIndexOf(".") + 1);
642  }
643  return new ObservableResult(id, type + " not supported", //NON-NLS
644  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
645  }
646 
647  // Evalutate the object
648  return evalObj.evaluate();
649  }
650 
651  @Override
652  public String getName() {
653  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
654  return name;
655  }
656 
657  @Override
658  public String getRelativeFilePath() {
659  return "stix.txt"; //NON-NLS
660  }
661 
662  @Override
663  public String getDescription() {
664  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
665  return desc;
666  }
667 
668  @Override
669  public JPanel getConfigurationPanel() {
670  configPanel = new STIXReportModuleConfigPanel();
671  return configPanel;
672  }
673 
674 }
ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing)
void generateReport(String baseReportDir, ReportProgressPanel progressPanel)
void processFile(String stixFile, ReportProgressPanel progressPanel)
ObservableResult evaluateObject(ObjectType obj, String spacing, String id)
static synchronized STIXReportModule getDefault()
void writeResultsToFile(Indicator ind, String resultStr, boolean found)
void saveResultsAsArtifacts(Indicator ind, ObservableResult result)
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
ObservableResult evaluateSingleObservable(Observable obs, String spacing)
List< EvalRegistryObj.RegistryFileInfo > registryFileData
synchronized static Logger getLogger(String name)
Definition: Logger.java:166
static void show(String title, String message, MessageType type, ActionListener actionListener)

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.