Autopsy  4.14.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 - 2018 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.report.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;
62 import org.sleuthkit.datamodel.TskCoreException;
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<>();
76  private Map<String, ObservableResult> idToResult = new HashMap<>();
77 
78  private List<EvalRegistryObj.RegistryFileInfo> registryFileData = null;
79 
80  private final boolean skipShortCircuit = true;
81 
82  // Hidden constructor for the report
83  private STIXReportModule() {
84  }
85 
86  // Get the default implementation of this report
87  public static synchronized STIXReportModule getDefault() {
88  if (instance == null) {
89  instance = new STIXReportModule();
90  }
91  return instance;
92  }
93 
98  @Override
99  @Messages({"STIXReportModule.srcModuleName.text=STIX Report"})
100  public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) {
101  // Start the progress bar and setup the report
102  progressPanel.setIndeterminate(false);
103  progressPanel.start();
104  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX"));
105  reportPath = baseReportDir + getRelativeFilePath();
106  File reportFile = new File(reportPath);
107  // Check if the user wants to display all output or just hits
108  reportAllResults = configPanel.getShowAllResults();
109 
110  // Keep track of whether any errors occur during processing
111  boolean hadErrors = false;
112 
113  // Process the file/directory name entry
114  String stixFileName = configPanel.getStixFile();
115 
116  if (stixFileName == null) {
117  logger.log(Level.SEVERE, "STIXReportModuleConfigPanel.stixFile not initialized "); //NON-NLS
118  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
119  new File(baseReportDir).delete();
120  return;
121  }
122  if (stixFileName.isEmpty()) {
123  logger.log(Level.SEVERE, "No STIX file/directory provided "); //NON-NLS
124  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
125  new File(baseReportDir).delete();
126  return;
127  }
128  File stixFile = new File(stixFileName);
129 
130  if (!stixFile.exists()) {
131  logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName)); //NON-NLS
132  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName));
133  new File(baseReportDir).delete();
134  return;
135  }
136 
137  try (BufferedWriter output = new BufferedWriter(new FileWriter(reportFile))) {
138 
139  // Create array of stix file(s)
140  File[] stixFiles;
141  if (stixFile.isFile()) {
142  stixFiles = new File[1];
143  stixFiles[0] = stixFile;
144  } else {
145  stixFiles = stixFile.listFiles();
146  }
147 
148  // Set the length of the progress bar - we increment twice for each file
149  progressPanel.setMaximumProgress(stixFiles.length * 2 + 1);
150 
151  // Process each STIX file
152  for (File file : stixFiles) {
153  if (progressPanel.getStatus() == ReportStatus.CANCELED) {
154  return;
155  }
156  try {
157  processFile(file.getAbsolutePath(), progressPanel, output);
158  } catch (TskCoreException | JAXBException ex) {
159  String errMsg = String.format("Unable to process STIX file %s", file);
160  logger.log(Level.SEVERE, errMsg, ex); //NON-NLS
161  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), errMsg));
162  hadErrors = true;
163  break;
164  }
165  // Clear out the ID maps before loading the next file
166  idToObjectMap = new HashMap<>();
167  idToResult = new HashMap<>();
168  }
169 
170  // Set the progress bar to done. If any errors occurred along the way, modify
171  // the "complete" message to indicate this.
172  Case.getCurrentCaseThrows().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), "");
173  if (hadErrors) {
174  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
175  } else {
176  progressPanel.complete(ReportStatus.COMPLETE);
177  }
178  } catch (IOException ex) {
179  logger.log(Level.SEVERE, "Unable to complete STIX report.", ex); //NON-NLS
180  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
181  } catch (TskCoreException | NoCurrentCaseException ex) {
182  logger.log(Level.SEVERE, "Unable to add report to database.", ex);
183  }
184  }
185 
196  private void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output) throws
197  JAXBException, TskCoreException {
198 
199  // Load the STIX file
200  STIXPackage stix;
201  stix = loadSTIXFile(stixFile);
202 
203  printFileHeader(stixFile, output);
204 
205  // Save any observables listed up front
206  processObservables(stix);
207  progressPanel.increment();
208 
209  // Make copies of the registry files
210  registryFileData = EvalRegistryObj.copyRegistryFiles();
211 
212  // Process the indicators
213  processIndicators(stix, output, progressPanel);
214  progressPanel.increment();
215 
216  }
217 
227  private STIXPackage loadSTIXFile(String stixFileName) throws JAXBException {
228  // Create STIXPackage object from xml.
229  File file = new File(stixFileName);
230  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
231  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
232  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
233  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
234  return stix;
235  }
236 
243  private void processObservables(STIXPackage stix) {
244  if (stix.getObservables() != null) {
245  List<Observable> obs = stix.getObservables().getObservables();
246  for (Observable o : obs) {
247  if (o.getId() != null) {
248  saveToObjectMap(o);
249  }
250  }
251  }
252  }
253 
262  private void processIndicators(STIXPackage stix, BufferedWriter output, ReportProgressPanel progressPanel) throws TskCoreException {
263  if (stix.getIndicators() != null) {
264  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
265  for (IndicatorBaseType t : s) {
266  if (t instanceof Indicator) {
267  Indicator ind = (Indicator) t;
268  if (ind.getObservable() != null) {
269  if (ind.getObservable().getObject() != null) {
270  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
271  if (result.isTrue() || reportAllResults) {
272  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
273  }
274  if (result.isTrue()) {
275  saveResultsAsArtifacts(ind, result, progressPanel);
276  }
277  } else if (ind.getObservable().getObservableComposition() != null) {
278  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
279 
280  if (result.isTrue() || reportAllResults) {
281  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
282  }
283  if (result.isTrue()) {
284  saveResultsAsArtifacts(ind, result, progressPanel);
285  }
286  }
287  }
288  }
289  }
290  }
291  }
292 
302  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result, ReportProgressPanel progressPanel) throws TskCoreException {
303 
304  if (result.getArtifacts() == null) {
305  return;
306  }
307 
308  // Count of how many artifacts have been created for this indicator.
309  int count = 0;
310 
311  for (StixArtifactData s : result.getArtifacts()) {
312 
313  // Figure out what name to use for this indicator. If it has a title,
314  // use that. Otherwise use the ID. If both are missing, use a
315  // generic heading.
316  if (ind.getTitle() != null) {
317  s.createArtifact(ind.getTitle());
318  } else if (ind.getId() != null) {
319  s.createArtifact(ind.getId().toString());
320  } else {
321  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
322  }
323 
324  // Trying to protect against the case where we end up with tons of artifacts
325  // for a single observable because the condition was not restrictive enough
326  count++;
327  if (count > 1000) {
328  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(),
329  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000"));
330  break;
331  }
332  }
333 
334  }
335 
345  private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) {
346  if (output != null) {
347  try {
348  if (found) {
349  output.write("----------------\r\n"
350  + "Found indicator:\r\n"); //NON-NLS
351  } else {
352  output.write("-----------------------\r\n"
353  + "Did not find indicator:\r\n"); //NON-NLS
354  }
355  if (ind.getTitle() != null) {
356  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
357  } else {
358  output.write("\r\n");
359  }
360  if (ind.getId() != null) {
361  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
362  }
363 
364  if (ind.getDescription() != null) {
365  String desc = ind.getDescription().getValue();
366  desc = desc.trim();
367  output.write("Description: " + desc + "\r\n"); //NON-NLS
368  }
369  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
370  } catch (IOException ex) {
371  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
372  }
373  }
374  }
375 
382  private void printFileHeader(String a_fileName, BufferedWriter output) {
383  if (output != null) {
384  try {
385  char[] chars = new char[a_fileName.length() + 8];
386  Arrays.fill(chars, '#');
387  String header = new String(chars);
388  output.write("\r\n" + header);
389  output.write("\r\n");
390  output.write("### " + a_fileName + " ###\r\n");
391  output.write(header + "\r\n\r\n");
392  } catch (IOException ex) {
393  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
394  }
395 
396  }
397 
398  }
399 
407  private String makeMapKey(Observable obs) {
408  QName idQ;
409  if (obs.getId() != null) {
410  idQ = obs.getId();
411  } else if (obs.getIdref() != null) {
412  idQ = obs.getIdref();
413  } else {
414  return "";
415  }
416 
417  return idQ.getLocalPart();
418  }
419 
425  private void saveToObjectMap(Observable obs) {
426 
427  if (obs.getObject() != null) {
428  idToObjectMap.put(makeMapKey(obs), obs.getObject());
429  }
430  }
431 
442  private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
443  if (comp.getOperator() == null) {
444  throw new TskCoreException("No operator found in composition"); //NON-NLS
445  }
446 
447  if (comp.getObservables() != null) {
448  List<Observable> obsList = comp.getObservables();
449 
450  // Split based on the type of composition (AND vs OR)
451  if (comp.getOperator() == OperatorTypeEnum.AND) {
452  ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
453  for (Observable o : obsList) {
454 
455  ObservableResult newResult; // The combined result for the composition
456  if (o.getObservableComposition() != null) {
457  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
458  if (result == null) {
459  result = newResult;
460  } else {
461  result.addResult(newResult, OperatorTypeEnum.AND);
462  }
463  } else {
464  newResult = evaluateSingleObservable(o, spacing + " ");
465  if (result == null) {
466  result = newResult;
467  } else {
468  result.addResult(newResult, OperatorTypeEnum.AND);
469  }
470  }
471 
472  if ((!skipShortCircuit) && !result.isFalse()) {
473  // For testing purposes (and maybe in general), may not want to short-circuit
474  return result;
475  }
476  }
477  // At this point, all comparisions should have been true (or indeterminate)
478  if (result == null) {
479  // This really shouldn't happen, but if we have an empty composition,
480  // indeterminate seems like a reasonable result
481  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
482  }
483 
484  return result;
485 
486  } else {
487  ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
488  for (Observable o : obsList) {
489 
490  ObservableResult newResult;// The combined result for the composition
491 
492  if (o.getObservableComposition() != null) {
493  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
494  if (result == null) {
495  result = newResult;
496  } else {
497  result.addResult(newResult, OperatorTypeEnum.OR);
498  }
499  } else {
500  newResult = evaluateSingleObservable(o, spacing + " ");
501  if (result == null) {
502  result = newResult;
503  } else {
504  result.addResult(newResult, OperatorTypeEnum.OR);
505  }
506  }
507 
508  if ((!skipShortCircuit) && result.isTrue()) {
509  // For testing (and maybe in general), may not want to short-circuit
510  return result;
511  }
512  }
513  // At this point, all comparisions were false (or indeterminate)
514  if (result == null) {
515  // This really shouldn't happen, but if we have an empty composition,
516  // indeterminate seems like a reasonable result
517  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
518  }
519 
520  return result;
521  }
522  } else {
523  throw new TskCoreException("No observables found in list"); //NON-NLS
524  }
525  }
526 
538  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
539 
540  // If we've already calculated this one, return the saved value
541  if (idToResult.containsKey(makeMapKey(obs))) {
542  return idToResult.get(makeMapKey(obs));
543  }
544 
545  if (obs.getIdref() == null) {
546 
547  // We should have the object data right here (as opposed to elsewhere in the STIX file).
548  // Save it to the map.
549  if (obs.getId() != null) {
550  saveToObjectMap(obs);
551  }
552 
553  if (obs.getObject() != null) {
554 
555  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
556  idToResult.put(makeMapKey(obs), result);
557  return result;
558  }
559  }
560 
561  if (idToObjectMap.containsKey(makeMapKey(obs))) {
562  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
563  idToResult.put(makeMapKey(obs), result);
564  return result;
565  }
566 
567  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
568  }
569 
580  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
581 
582  EvaluatableObject evalObj;
583 
584  if (obj.getProperties() instanceof FileObjectType) {
585  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
586  } else if (obj.getProperties() instanceof Address) {
587  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
588  } else if (obj.getProperties() instanceof URIObjectType) {
589  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
590  } else if (obj.getProperties() instanceof EmailMessage) {
591  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
592  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
593  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
594  } else if (obj.getProperties() instanceof AccountObjectType) {
595  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
596  } else if (obj.getProperties() instanceof SystemObjectType) {
597  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
598  } else if (obj.getProperties() instanceof URLHistory) {
599  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
600  } else if (obj.getProperties() instanceof DomainName) {
601  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
602  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
603  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
604  } else {
605  // Try to get the object type as a string
606  String type = obj.getProperties().toString();
607  type = type.substring(0, type.indexOf("@"));
608  if ((type.lastIndexOf(".") + 1) < type.length()) {
609  type = type.substring(type.lastIndexOf(".") + 1);
610  }
611  return new ObservableResult(id, type + " not supported", //NON-NLS
612  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
613  }
614 
615  // Evalutate the object
616  return evalObj.evaluate();
617  }
618 
619  @Override
620  public String getName() {
621  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
622  return name;
623  }
624 
625  @Override
626  public String getRelativeFilePath() {
627  return "stix.txt"; //NON-NLS
628  }
629 
630  @Override
631  public String getDescription() {
632  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
633  return desc;
634  }
635 
636  @Override
637  public JPanel getConfigurationPanel() {
638  initializePanel();
639  return configPanel;
640  }
641 
642  private void initializePanel() {
643  if (configPanel == null) {
644  configPanel = new STIXReportModuleConfigPanel();
645  }
646  }
647 
653  @Override
655  return new STIXReportModuleSettings();
656  }
657 
663  @Override
665  initializePanel();
666  return configPanel.getConfiguration();
667  }
668 
674  @Override
675  public void setConfiguration(ReportModuleSettings settings) {
676  initializePanel();
677  if (settings == null || settings instanceof NoReportModuleSettings) {
678  configPanel.setConfiguration((STIXReportModuleSettings) getDefaultConfiguration());
679  return;
680  }
681 
682  if (settings instanceof STIXReportModuleSettings) {
683  configPanel.setConfiguration((STIXReportModuleSettings) settings);
684  return;
685  }
686 
687  throw new IllegalArgumentException("Expected settings argument to be an instance of STIXReportModuleSettings");
688  }
689 
690 }
void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output)
void saveResultsAsArtifacts(Indicator ind, ObservableResult result, ReportProgressPanel progressPanel)
void generateReport(String baseReportDir, ReportProgressPanel progressPanel)
void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output)
ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing)
void printFileHeader(String a_fileName, BufferedWriter output)
ObservableResult evaluateSingleObservable(Observable obs, String spacing)
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1630
List< EvalRegistryObj.RegistryFileInfo > registryFileData
ObservableResult evaluateObject(ObjectType obj, String spacing, String id)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void processIndicators(STIXPackage stix, BufferedWriter output, ReportProgressPanel progressPanel)

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