Autopsy  3.1
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;
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 
225  private void processFile(String stixFile, ReportProgressPanel progressPanel) throws
227 
228  // Load the STIX file
229  STIXPackage stix;
230  stix = loadSTIXFile(stixFile);
231 
232  printFileHeader(stixFile);
233 
234  // Save any observables listed up front
235  processObservables(stix);
236  progressPanel.increment();
237 
238  // Make copies of the registry files
239  registryFileData = EvalRegistryObj.copyRegistryFiles();
240 
241  // Process the indicators
242  processIndicators(stix);
243  progressPanel.increment();
244 
245  }
246 
254  private STIXPackage loadSTIXFile(String stixFileName) throws TskCoreException {
255  try {
256  // Create STIXPackage object from xml.
257  File file = new File(stixFileName);
258  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
259  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
260  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
261  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
262  return stix;
263  } catch (JAXBException ex) {
264  logger.log(Level.SEVERE, String.format("Unable to load STIX file %s", stixFileName), ex.getLocalizedMessage()); //NON-NLS
265  throw new TskCoreException("Error loading STIX file (" + ex.toString() + ")"); //NON-NLS
266  }
267  }
268 
275  private void processObservables(STIXPackage stix) {
276  if (stix.getObservables() != null) {
277  List<Observable> obs = stix.getObservables().getObservables();
278  for (Observable o : obs) {
279  if (o.getId() != null) {
280  saveToObjectMap(o);
281  }
282  }
283  }
284  }
285 
292  private void processIndicators(STIXPackage stix) throws TskCoreException {
293  if (stix.getIndicators() != null) {
294  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
295  for (IndicatorBaseType t : s) {
296  if (t instanceof Indicator) {
297  Indicator ind = (Indicator) t;
298  if (ind.getObservable() != null) {
299  if (ind.getObservable().getObject() != null) {
300  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
301  if (result.isTrue() || reportAllResults) {
302  writeResultsToFile(ind, result.getDescription(), result.isTrue());
303  }
304  if (result.isTrue()) {
305  saveResultsAsArtifacts(ind, result);
306  }
307  } else if (ind.getObservable().getObservableComposition() != null) {
308  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
309 
310  if (result.isTrue() || reportAllResults) {
311  writeResultsToFile(ind, result.getDescription(), result.isTrue());
312  }
313  if (result.isTrue()) {
314  saveResultsAsArtifacts(ind, result);
315  }
316  }
317  }
318  }
319  }
320  }
321  }
322 
330  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result) throws TskCoreException {
331 
332  if (result.getArtifacts() == null) {
333  return;
334  }
335 
336  // Count of how many artifacts have been created for this indicator.
337  int count = 0;
338 
339  for (StixArtifactData s : result.getArtifacts()) {
340 
341  // Figure out what name to use for this indicator. If it has a title,
342  // use that. Otherwise use the ID. If both are missing, use a
343  // generic heading.
344  if (ind.getTitle() != null) {
345  s.createArtifact(ind.getTitle());
346  } else if (ind.getId() != null) {
347  s.createArtifact(ind.getId().toString());
348  } else {
349  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
350  }
351 
352  // Trying to protect against the case where we end up with tons of artifacts
353  // for a single observable because the condition was not restrictive enough
354  count++;
355  if (count > 1000) {
356  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
357  NbBundle.getMessage(this.getClass(),
358  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000",
359  ind.getId()),
361  break;
362  }
363  }
364 
365  }
366 
374  private void writeResultsToFile(Indicator ind, String resultStr, boolean found) {
375  if (output != null) {
376  try {
377  if (found) {
378  output.write("----------------\r\n"
379  + "Found indicator:\r\n"); //NON-NLS
380  } else {
381  output.write("-----------------------\r\n"
382  + "Did not find indicator:\r\n"); //NON-NLS
383  }
384  if (ind.getTitle() != null) {
385  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
386  } else {
387  output.write("\r\n");
388  }
389  if (ind.getId() != null) {
390  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
391  }
392 
393  if (ind.getDescription() != null) {
394  String desc = ind.getDescription().getValue();
395  desc = desc.trim();
396  output.write("Description: " + desc + "\r\n"); //NON-NLS
397  }
398  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
399  } catch (IOException ex) {
400  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
401  }
402  }
403  }
404 
410  private void printFileHeader(String a_fileName) {
411  if (output != null) {
412  try {
413  char[] chars = new char[a_fileName.length() + 8];
414  Arrays.fill(chars, '#');
415  String header = new String(chars);
416  output.write("\r\n" + header);
417  output.write("\r\n");
418  output.write("### " + a_fileName + " ###\r\n");
419  output.write(header + "\r\n\r\n");
420  } catch (IOException ex) {
421  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
422  }
423 
424  }
425 
426  }
427 
434  private String makeMapKey(Observable obs) {
435  QName idQ;
436  if (obs.getId() != null) {
437  idQ = obs.getId();
438  } else if (obs.getIdref() != null) {
439  idQ = obs.getIdref();
440  } else {
441  return "";
442  }
443 
444  return idQ.getLocalPart();
445  }
446 
452  private void saveToObjectMap(Observable obs) {
453 
454  if (obs.getObject() != null) {
455  idToObjectMap.put(makeMapKey(obs), obs.getObject());
456  }
457  }
458 
467  private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
468  if (comp.getOperator() == null) {
469  throw new TskCoreException("No operator found in composition"); //NON-NLS
470  }
471 
472  if (comp.getObservables() != null) {
473  List<Observable> obsList = comp.getObservables();
474 
475  // Split based on the type of composition (AND vs OR)
476  if (comp.getOperator() == OperatorTypeEnum.AND) {
477  ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
478  for (Observable o : obsList) {
479 
480  ObservableResult newResult; // The combined result for the composition
481  if (o.getObservableComposition() != null) {
482  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
483  if (result == null) {
484  result = newResult;
485  } else {
486  result.addResult(newResult, OperatorTypeEnum.AND);
487  }
488  } else {
489  newResult = evaluateSingleObservable(o, spacing + " ");
490  if (result == null) {
491  result = newResult;
492  } else {
493  result.addResult(newResult, OperatorTypeEnum.AND);
494  }
495  }
496 
497  if ((!skipShortCircuit) && !result.isFalse()) {
498  // For testing purposes (and maybe in general), may not want to short-circuit
499  return result;
500  }
501  }
502  // At this point, all comparisions should have been true (or indeterminate)
503  if (result == null) {
504  // This really shouldn't happen, but if we have an empty composition,
505  // indeterminate seems like a reasonable result
506  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
507  }
508 
509  return result;
510 
511  } else {
512  ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
513  for (Observable o : obsList) {
514 
515  ObservableResult newResult;// The combined result for the composition
516 
517  if (o.getObservableComposition() != null) {
518  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
519  if (result == null) {
520  result = newResult;
521  } else {
522  result.addResult(newResult, OperatorTypeEnum.OR);
523  }
524  } else {
525  newResult = evaluateSingleObservable(o, spacing + " ");
526  if (result == null) {
527  result = newResult;
528  } else {
529  result.addResult(newResult, OperatorTypeEnum.OR);
530  }
531  }
532 
533  if ((!skipShortCircuit) && result.isTrue()) {
534  // For testing (and maybe in general), may not want to short-circuit
535  return result;
536  }
537  }
538  // At this point, all comparisions were false (or indeterminate)
539  if (result == null) {
540  // This really shouldn't happen, but if we have an empty composition,
541  // indeterminate seems like a reasonable result
542  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
543  }
544 
545  return result;
546  }
547  } else {
548  throw new TskCoreException("No observables found in list"); //NON-NLS
549  }
550  }
551 
561  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
562 
563  // If we've already calculated this one, return the saved value
564  if (idToResult.containsKey(makeMapKey(obs))) {
565  return idToResult.get(makeMapKey(obs));
566  }
567 
568  if (obs.getIdref() == null) {
569 
570  // We should have the object data right here (as opposed to elsewhere in the STIX file).
571  // Save it to the map.
572  if (obs.getId() != null) {
573  saveToObjectMap(obs);
574  }
575 
576  if (obs.getObject() != null) {
577 
578  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
579  idToResult.put(makeMapKey(obs), result);
580  return result;
581  }
582  }
583 
584  if (idToObjectMap.containsKey(makeMapKey(obs))) {
585  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
586  idToResult.put(makeMapKey(obs), result);
587  return result;
588  }
589 
590  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
591  }
592 
601  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
602 
603  EvaluatableObject evalObj;
604 
605  if (obj.getProperties() instanceof FileObjectType) {
606  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
607  } else if (obj.getProperties() instanceof Address) {
608  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
609  } else if (obj.getProperties() instanceof URIObjectType) {
610  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
611  } else if (obj.getProperties() instanceof EmailMessage) {
612  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
613  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
614  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
615  } else if (obj.getProperties() instanceof AccountObjectType) {
616  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
617  } else if (obj.getProperties() instanceof SystemObjectType) {
618  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
619  } else if (obj.getProperties() instanceof URLHistory) {
620  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
621  } else if (obj.getProperties() instanceof DomainName) {
622  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
623  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
624  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
625  } else {
626  // Try to get the object type as a string
627  String type = obj.getProperties().toString();
628  type = type.substring(0, type.indexOf("@"));
629  if ((type.lastIndexOf(".") + 1) < type.length()) {
630  type = type.substring(type.lastIndexOf(".") + 1);
631  }
632  return new ObservableResult(id, type + " not supported", //NON-NLS
633  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
634  }
635 
636  // Evalutate the object
637  return evalObj.evaluate();
638  }
639 
640  @Override
641  public String getName() {
642  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
643  return name;
644  }
645 
646  @Override
647  public String getRelativeFilePath() {
648  return "stix.txt"; //NON-NLS
649  }
650 
651  @Override
652  public String getDescription() {
653  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
654  return desc;
655  }
656 
657  @Override
658  public JPanel getConfigurationPanel() {
659  configPanel = new STIXReportModuleConfigPanel();
660  return configPanel;
661  }
662 
663 }
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
static void show(String title, String message, MessageType type, ActionListener actionListener)
static Logger getLogger(String name)
Definition: Logger.java:131

Copyright © 2012-2015 Basis Technology. Generated on: Mon Oct 19 2015
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.