Autopsy  4.4.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.BufferedWriter;
22 import java.io.File;
23 import java.io.FileWriter;
24 import java.io.IOException;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.logging.Level;
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 import org.mitre.cybox.cybox_2.ObjectType;
36 import org.mitre.cybox.cybox_2.Observable;
37 import org.mitre.cybox.cybox_2.ObservableCompositionType;
38 import org.mitre.cybox.cybox_2.OperatorTypeEnum;
39 import org.mitre.cybox.objects.AccountObjectType;
40 import org.mitre.cybox.objects.Address;
41 import org.mitre.cybox.objects.DomainName;
42 import org.mitre.cybox.objects.EmailMessage;
43 import org.mitre.cybox.objects.FileObjectType;
44 import org.mitre.cybox.objects.SystemObjectType;
45 import org.mitre.cybox.objects.URIObjectType;
46 import org.mitre.cybox.objects.URLHistory;
47 import org.mitre.cybox.objects.WindowsNetworkShare;
48 import org.mitre.cybox.objects.WindowsRegistryKey;
49 import org.mitre.stix.common_1.IndicatorBaseType;
50 import org.mitre.stix.indicator_2.Indicator;
51 import org.mitre.stix.stix_1.STIXPackage;
52 import org.openide.util.NbBundle;
53 import org.openide.util.NbBundle.Messages;
61 import org.sleuthkit.datamodel.TskCoreException;
62 
66 public class STIXReportModule implements GeneralReportModule {
67 
68  private static final Logger logger = Logger.getLogger(STIXReportModule.class.getName());
70  private static STIXReportModule instance = null;
71  private String reportPath;
72  private boolean reportAllResults;
73 
74  private Map<String, ObjectType> idToObjectMap = new HashMap<String, ObjectType>();
75  private Map<String, ObservableResult> idToResult = new HashMap<String, ObservableResult>();
76 
77  private List<EvalRegistryObj.RegistryFileInfo> registryFileData = null;
78 
79  private final boolean skipShortCircuit = true;
80 
81  // Hidden constructor for the report
82  private STIXReportModule() {
83  }
84 
85  // Get the default implementation of this report
86  public static synchronized STIXReportModule getDefault() {
87  if (instance == null) {
88  instance = new STIXReportModule();
89  }
90  return instance;
91  }
92 
97  @Override
98  @Messages({"STIXReportModule.srcModuleName.text=STIX Report"})
99  public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) {
100  // Start the progress bar and setup the report
101  progressPanel.setIndeterminate(false);
102  progressPanel.start();
103  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX"));
104  reportPath = baseReportDir + getRelativeFilePath();
105  File reportFile = new File(reportPath);
106  // Check if the user wants to display all output or just hits
107  reportAllResults = configPanel.getShowAllResults();
108 
109  // Keep track of whether any errors occur during processing
110  boolean hadErrors = false;
111 
112  // Process the file/directory name entry
113  String stixFileName = configPanel.getStixFile();
114 
115  if (stixFileName == null) {
116  logger.log(Level.SEVERE, "STIXReportModuleConfigPanel.stixFile not initialized "); //NON-NLS
118  NbBundle.getMessage(this.getClass(), "STIXReportModule.notifyErr.noFildDirProvided"));
119  progressPanel.complete(ReportStatus.ERROR);
120  progressPanel.updateStatusLabel(
121  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
122  new File(baseReportDir).delete();
123  return;
124  }
125  if (stixFileName.isEmpty()) {
126  logger.log(Level.SEVERE, "No STIX file/directory provided "); //NON-NLS
128  NbBundle.getMessage(this.getClass(), "STIXReportModule.notifyErr.noFildDirProvided"));
129  progressPanel.complete(ReportStatus.ERROR);
130  progressPanel.updateStatusLabel(
131  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
132  new File(baseReportDir).delete();
133  return;
134  }
135  File stixFile = new File(stixFileName);
136 
137  if (!stixFile.exists()) {
138  logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName)); //NON-NLS
139  MessageNotifyUtil.Message.error(NbBundle.getMessage(this.getClass(),
140  "STIXReportModule.notifyMsg.unableToOpenFileDir",
141  stixFileName));
142  progressPanel.complete(ReportStatus.ERROR);
143  progressPanel.updateStatusLabel(
144  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName));
145  new File(baseReportDir).delete();
146  return;
147  }
148 
149  try (BufferedWriter output = new BufferedWriter(new FileWriter(reportFile))) {
150  // Store the path
151  ModuleSettings.setConfigSetting("STIX", "defaultPath", stixFileName); //NON-NLS
152 
153  // Create array of stix file(s)
154  File[] stixFiles;
155  if (stixFile.isFile()) {
156  stixFiles = new File[1];
157  stixFiles[0] = stixFile;
158  } else {
159  stixFiles = stixFile.listFiles();
160  }
161 
162  // Set the length of the progress bar - we increment twice for each file
163  progressPanel.setMaximumProgress(stixFiles.length * 2 + 1);
164 
165  // Process each STIX file
166  for (File file : stixFiles) {
167  if (progressPanel.getStatus() == ReportStatus.CANCELED) {
168  return;
169  }
170  try {
171  processFile(file.getAbsolutePath(), progressPanel, output);
172  } catch (TskCoreException | JAXBException ex) {
173  String errMsg = String.format("Unable to process STIX file %s", file);
174  logger.log(Level.SEVERE, errMsg, ex); //NON-NLS
175  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
176  errMsg,
178  hadErrors = true;
179  break;
180  }
181  // Clear out the ID maps before loading the next file
182  idToObjectMap = new HashMap<String, ObjectType>();
183  idToResult = new HashMap<String, ObservableResult>();
184  }
185 
186  // Set the progress bar to done. If any errors occurred along the way, modify
187  // the "complete" message to indicate this.
188  Case.getCurrentCase().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), "");
189  if (hadErrors) {
190  progressPanel.complete(ReportStatus.ERROR);
191  progressPanel.updateStatusLabel(
192  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
193  } else {
194  progressPanel.complete(ReportStatus.COMPLETE);
195  }
196  } catch (IOException ex) {
197  logger.log(Level.SEVERE, "Unable to complete STIX report.", ex); //NON-NLS
198  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
199  NbBundle.getMessage(this.getClass(),
200  "STIXReportModule.notifyMsg.unableToOpenReportFile"),
202  progressPanel.complete(ReportStatus.ERROR);
203  progressPanel.updateStatusLabel(
204  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
205  } catch (TskCoreException ex) {
206  logger.log(Level.SEVERE, "Unable to add report to database.", ex);
207  }
208  }
209 
220  private void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output) throws
221  JAXBException, TskCoreException {
222 
223  // Load the STIX file
224  STIXPackage stix;
225  stix = loadSTIXFile(stixFile);
226 
227  printFileHeader(stixFile, output);
228 
229  // Save any observables listed up front
230  processObservables(stix);
231  progressPanel.increment();
232 
233  // Make copies of the registry files
234  registryFileData = EvalRegistryObj.copyRegistryFiles();
235 
236  // Process the indicators
237  processIndicators(stix, output);
238  progressPanel.increment();
239 
240  }
241 
251  private STIXPackage loadSTIXFile(String stixFileName) throws JAXBException {
252  // Create STIXPackage object from xml.
253  File file = new File(stixFileName);
254  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
255  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
256  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
257  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
258  return stix;
259  }
260 
267  private void processObservables(STIXPackage stix) {
268  if (stix.getObservables() != null) {
269  List<Observable> obs = stix.getObservables().getObservables();
270  for (Observable o : obs) {
271  if (o.getId() != null) {
272  saveToObjectMap(o);
273  }
274  }
275  }
276  }
277 
285  private void processIndicators(STIXPackage stix, BufferedWriter output) throws TskCoreException {
286  if (stix.getIndicators() != null) {
287  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
288  for (IndicatorBaseType t : s) {
289  if (t instanceof Indicator) {
290  Indicator ind = (Indicator) t;
291  if (ind.getObservable() != null) {
292  if (ind.getObservable().getObject() != null) {
293  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
294  if (result.isTrue() || reportAllResults) {
295  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
296  }
297  if (result.isTrue()) {
298  saveResultsAsArtifacts(ind, result);
299  }
300  } else if (ind.getObservable().getObservableComposition() != null) {
301  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
302 
303  if (result.isTrue() || reportAllResults) {
304  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
305  }
306  if (result.isTrue()) {
307  saveResultsAsArtifacts(ind, result);
308  }
309  }
310  }
311  }
312  }
313  }
314  }
315 
324  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result) throws TskCoreException {
325 
326  if (result.getArtifacts() == null) {
327  return;
328  }
329 
330  // Count of how many artifacts have been created for this indicator.
331  int count = 0;
332 
333  for (StixArtifactData s : result.getArtifacts()) {
334 
335  // Figure out what name to use for this indicator. If it has a title,
336  // use that. Otherwise use the ID. If both are missing, use a
337  // generic heading.
338  if (ind.getTitle() != null) {
339  s.createArtifact(ind.getTitle());
340  } else if (ind.getId() != null) {
341  s.createArtifact(ind.getId().toString());
342  } else {
343  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
344  }
345 
346  // Trying to protect against the case where we end up with tons of artifacts
347  // for a single observable because the condition was not restrictive enough
348  count++;
349  if (count > 1000) {
350  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
351  NbBundle.getMessage(this.getClass(),
352  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000",
353  ind.getId()),
355  break;
356  }
357  }
358 
359  }
360 
370  private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) {
371  if (output != null) {
372  try {
373  if (found) {
374  output.write("----------------\r\n"
375  + "Found indicator:\r\n"); //NON-NLS
376  } else {
377  output.write("-----------------------\r\n"
378  + "Did not find indicator:\r\n"); //NON-NLS
379  }
380  if (ind.getTitle() != null) {
381  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
382  } else {
383  output.write("\r\n");
384  }
385  if (ind.getId() != null) {
386  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
387  }
388 
389  if (ind.getDescription() != null) {
390  String desc = ind.getDescription().getValue();
391  desc = desc.trim();
392  output.write("Description: " + desc + "\r\n"); //NON-NLS
393  }
394  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
395  } catch (IOException ex) {
396  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
397  }
398  }
399  }
400 
407  private void printFileHeader(String a_fileName, BufferedWriter output) {
408  if (output != null) {
409  try {
410  char[] chars = new char[a_fileName.length() + 8];
411  Arrays.fill(chars, '#');
412  String header = new String(chars);
413  output.write("\r\n" + header);
414  output.write("\r\n");
415  output.write("### " + a_fileName + " ###\r\n");
416  output.write(header + "\r\n\r\n");
417  } catch (IOException ex) {
418  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
419  }
420 
421  }
422 
423  }
424 
432  private String makeMapKey(Observable obs) {
433  QName idQ;
434  if (obs.getId() != null) {
435  idQ = obs.getId();
436  } else if (obs.getIdref() != null) {
437  idQ = obs.getIdref();
438  } else {
439  return "";
440  }
441 
442  return idQ.getLocalPart();
443  }
444 
450  private void saveToObjectMap(Observable obs) {
451 
452  if (obs.getObject() != null) {
453  idToObjectMap.put(makeMapKey(obs), obs.getObject());
454  }
455  }
456 
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 
563  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
564 
565  // If we've already calculated this one, return the saved value
566  if (idToResult.containsKey(makeMapKey(obs))) {
567  return idToResult.get(makeMapKey(obs));
568  }
569 
570  if (obs.getIdref() == null) {
571 
572  // We should have the object data right here (as opposed to elsewhere in the STIX file).
573  // Save it to the map.
574  if (obs.getId() != null) {
575  saveToObjectMap(obs);
576  }
577 
578  if (obs.getObject() != null) {
579 
580  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
581  idToResult.put(makeMapKey(obs), result);
582  return result;
583  }
584  }
585 
586  if (idToObjectMap.containsKey(makeMapKey(obs))) {
587  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
588  idToResult.put(makeMapKey(obs), result);
589  return result;
590  }
591 
592  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
593  }
594 
605  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
606 
607  EvaluatableObject evalObj;
608 
609  if (obj.getProperties() instanceof FileObjectType) {
610  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
611  } else if (obj.getProperties() instanceof Address) {
612  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
613  } else if (obj.getProperties() instanceof URIObjectType) {
614  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
615  } else if (obj.getProperties() instanceof EmailMessage) {
616  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
617  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
618  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
619  } else if (obj.getProperties() instanceof AccountObjectType) {
620  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
621  } else if (obj.getProperties() instanceof SystemObjectType) {
622  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
623  } else if (obj.getProperties() instanceof URLHistory) {
624  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
625  } else if (obj.getProperties() instanceof DomainName) {
626  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
627  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
628  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
629  } else {
630  // Try to get the object type as a string
631  String type = obj.getProperties().toString();
632  type = type.substring(0, type.indexOf("@"));
633  if ((type.lastIndexOf(".") + 1) < type.length()) {
634  type = type.substring(type.lastIndexOf(".") + 1);
635  }
636  return new ObservableResult(id, type + " not supported", //NON-NLS
637  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
638  }
639 
640  // Evalutate the object
641  return evalObj.evaluate();
642  }
643 
644  @Override
645  public String getName() {
646  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
647  return name;
648  }
649 
650  @Override
651  public String getRelativeFilePath() {
652  return "stix.txt"; //NON-NLS
653  }
654 
655  @Override
656  public String getDescription() {
657  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
658  return desc;
659  }
660 
661  @Override
662  public JPanel getConfigurationPanel() {
663  configPanel = new STIXReportModuleConfigPanel();
664  return configPanel;
665  }
666 
667 }
ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing)
void generateReport(String baseReportDir, ReportProgressPanel progressPanel)
ObservableResult evaluateObject(ObjectType obj, String spacing, String id)
void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output)
static synchronized STIXReportModule getDefault()
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1438
void saveResultsAsArtifacts(Indicator ind, ObservableResult result)
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
void printFileHeader(String a_fileName, BufferedWriter output)
ObservableResult evaluateSingleObservable(Observable obs, String spacing)
List< EvalRegistryObj.RegistryFileInfo > registryFileData
void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
static void show(String title, String message, MessageType type, ActionListener actionListener)
void processIndicators(STIXPackage stix, BufferedWriter output)

Copyright © 2012-2016 Basis Technology. Generated on: Fri Sep 29 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.