Autopsy  4.17.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 - 2020 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;
63 import org.sleuthkit.datamodel.TskCoreException;
64 
68 public class STIXReportModule implements GeneralReportModule {
69 
70  private static final Logger logger = Logger.getLogger(STIXReportModule.class.getName());
72  private static STIXReportModule instance = null;
73  private String reportPath;
74  private boolean reportAllResults;
75 
76  private Map<String, ObjectType> idToObjectMap = new HashMap<>();
77  private Map<String, ObservableResult> idToResult = new HashMap<>();
78 
79  private List<EvalRegistryObj.RegistryFileInfo> registryFileData = null;
80 
81  private final boolean skipShortCircuit = true;
82 
83  // Hidden constructor for the report
84  private STIXReportModule() {
85  }
86 
87  // Get the default implementation of this report
88  public static synchronized STIXReportModule getDefault() {
89  if (instance == null) {
90  instance = new STIXReportModule();
91  }
92  return instance;
93  }
94 
99  @Override
100  @Messages({"STIXReportModule.srcModuleName.text=STIX Report"})
101  public void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel) {
102  // Start the progress bar and setup the report
103  progressPanel.setIndeterminate(false);
104  progressPanel.start();
105  String baseReportDir = settings.getReportDirectoryPath();
106  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX"));
107  reportPath = baseReportDir + getRelativeFilePath();
108  File reportFile = new File(reportPath);
109  // Check if the user wants to display all output or just hits
110  reportAllResults = configPanel.getShowAllResults();
111 
112  // Keep track of whether any errors occur during processing
113  boolean hadErrors = false;
114 
115  // Process the file/directory name entry
116  String stixFileName = configPanel.getStixFile();
117 
118  if (stixFileName == null) {
119  logger.log(Level.SEVERE, "STIXReportModuleConfigPanel.stixFile not initialized "); //NON-NLS
120  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
121  new File(baseReportDir).delete();
122  return;
123  }
124  if (stixFileName.isEmpty()) {
125  logger.log(Level.SEVERE, "No STIX file/directory provided "); //NON-NLS
126  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
127  new File(baseReportDir).delete();
128  return;
129  }
130  File stixFile = new File(stixFileName);
131 
132  if (!stixFile.exists()) {
133  logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName)); //NON-NLS
134  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName));
135  new File(baseReportDir).delete();
136  return;
137  }
138 
139  try (BufferedWriter output = new BufferedWriter(new FileWriter(reportFile))) {
140 
141  // Create array of stix file(s)
142  File[] stixFiles;
143  if (stixFile.isFile()) {
144  stixFiles = new File[1];
145  stixFiles[0] = stixFile;
146  } else {
147  stixFiles = stixFile.listFiles();
148  }
149 
150  // Set the length of the progress bar - we increment twice for each file
151  progressPanel.setMaximumProgress(stixFiles.length * 2 + 1);
152 
153  // Process each STIX file
154  for (File file : stixFiles) {
155  if (progressPanel.getStatus() == ReportStatus.CANCELED) {
156  return;
157  }
158  try {
159  processFile(file.getAbsolutePath(), progressPanel, output);
160  } catch (TskCoreException | JAXBException ex) {
161  String errMsg = String.format("Unable to process STIX file %s", file);
162  logger.log(Level.SEVERE, errMsg, ex); //NON-NLS
163  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), errMsg));
164  hadErrors = true;
165  break;
166  }
167  // Clear out the ID maps before loading the next file
168  idToObjectMap = new HashMap<>();
169  idToResult = new HashMap<>();
170  }
171 
172  // Set the progress bar to done. If any errors occurred along the way, modify
173  // the "complete" message to indicate this.
174  Case.getCurrentCaseThrows().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), "");
175  if (hadErrors) {
176  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
177  } else {
178  progressPanel.complete(ReportStatus.COMPLETE);
179  }
180  } catch (IOException ex) {
181  logger.log(Level.SEVERE, "Unable to complete STIX report.", ex); //NON-NLS
182  progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
183  } catch (TskCoreException | NoCurrentCaseException ex) {
184  logger.log(Level.SEVERE, "Unable to add report to database.", ex);
185  }
186  }
187 
198  private void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output) throws
199  JAXBException, TskCoreException {
200 
201  // Load the STIX file
202  STIXPackage stix;
203  stix = loadSTIXFile(stixFile);
204 
205  printFileHeader(stixFile, output);
206 
207  // Save any observables listed up front
208  processObservables(stix);
209  progressPanel.increment();
210 
211  // Make copies of the registry files
212  registryFileData = EvalRegistryObj.copyRegistryFiles();
213 
214  // Process the indicators
215  processIndicators(stix, output, progressPanel);
216  progressPanel.increment();
217 
218  }
219 
229  private STIXPackage loadSTIXFile(String stixFileName) throws JAXBException {
230  // Create STIXPackage object from xml.
231  // See JIRA-6958 for details about class loading and jaxb.
232  ClassLoader original = Thread.currentThread().getContextClassLoader();
233  try {
234  Thread.currentThread().setContextClassLoader(STIXReportModule.class.getClassLoader());
235  File file = new File(stixFileName);
236  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
237  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
238  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
239  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
240  return stix;
241  } finally {
242  Thread.currentThread().setContextClassLoader(original);
243  }
244  }
245 
252  private void processObservables(STIXPackage stix) {
253  if (stix.getObservables() != null) {
254  List<Observable> obs = stix.getObservables().getObservables();
255  for (Observable o : obs) {
256  if (o.getId() != null) {
257  saveToObjectMap(o);
258  }
259  }
260  }
261  }
262 
271  private void processIndicators(STIXPackage stix, BufferedWriter output, ReportProgressPanel progressPanel) throws TskCoreException {
272  if (stix.getIndicators() != null) {
273  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
274  for (IndicatorBaseType t : s) {
275  if (t instanceof Indicator) {
276  Indicator ind = (Indicator) t;
277  if (ind.getObservable() != null) {
278  if (ind.getObservable().getObject() != null) {
279  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
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  } else if (ind.getObservable().getObservableComposition() != null) {
287  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
288 
289  if (result.isTrue() || reportAllResults) {
290  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
291  }
292  if (result.isTrue()) {
293  saveResultsAsArtifacts(ind, result, progressPanel);
294  }
295  }
296  }
297  }
298  }
299  }
300  }
301 
311  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result, ReportProgressPanel progressPanel) throws TskCoreException {
312 
313  if (result.getArtifacts() == null) {
314  return;
315  }
316 
317  // Count of how many artifacts have been created for this indicator.
318  int count = 0;
319 
320  for (StixArtifactData s : result.getArtifacts()) {
321 
322  // Figure out what name to use for this indicator. If it has a title,
323  // use that. Otherwise use the ID. If both are missing, use a
324  // generic heading.
325  if (ind.getTitle() != null) {
326  s.createArtifact(ind.getTitle());
327  } else if (ind.getId() != null) {
328  s.createArtifact(ind.getId().toString());
329  } else {
330  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
331  }
332 
333  // Trying to protect against the case where we end up with tons of artifacts
334  // for a single observable because the condition was not restrictive enough
335  count++;
336  if (count > 1000) {
337  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(),
338  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000"));
339  break;
340  }
341  }
342 
343  }
344 
354  private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) {
355  if (output != null) {
356  try {
357  if (found) {
358  output.write("----------------\r\n"
359  + "Found indicator:\r\n"); //NON-NLS
360  } else {
361  output.write("-----------------------\r\n"
362  + "Did not find indicator:\r\n"); //NON-NLS
363  }
364  if (ind.getTitle() != null) {
365  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
366  } else {
367  output.write("\r\n");
368  }
369  if (ind.getId() != null) {
370  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
371  }
372 
373  if (ind.getDescription() != null) {
374  String desc = ind.getDescription().getValue();
375  desc = desc.trim();
376  output.write("Description: " + desc + "\r\n"); //NON-NLS
377  }
378  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
379  } catch (IOException ex) {
380  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
381  }
382  }
383  }
384 
391  private void printFileHeader(String a_fileName, BufferedWriter output) {
392  if (output != null) {
393  try {
394  char[] chars = new char[a_fileName.length() + 8];
395  Arrays.fill(chars, '#');
396  String header = new String(chars);
397  output.write("\r\n" + header);
398  output.write("\r\n");
399  output.write("### " + a_fileName + " ###\r\n");
400  output.write(header + "\r\n\r\n");
401  } catch (IOException ex) {
402  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
403  }
404 
405  }
406 
407  }
408 
416  private String makeMapKey(Observable obs) {
417  QName idQ;
418  if (obs.getId() != null) {
419  idQ = obs.getId();
420  } else if (obs.getIdref() != null) {
421  idQ = obs.getIdref();
422  } else {
423  return "";
424  }
425 
426  return idQ.getLocalPart();
427  }
428 
434  private void saveToObjectMap(Observable obs) {
435 
436  if (obs.getObject() != null) {
437  idToObjectMap.put(makeMapKey(obs), obs.getObject());
438  }
439  }
440 
451  private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
452  if (comp.getOperator() == null) {
453  throw new TskCoreException("No operator found in composition"); //NON-NLS
454  }
455 
456  if (comp.getObservables() != null) {
457  List<Observable> obsList = comp.getObservables();
458 
459  // Split based on the type of composition (AND vs OR)
460  if (comp.getOperator() == OperatorTypeEnum.AND) {
461  ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
462  for (Observable o : obsList) {
463 
464  ObservableResult newResult; // The combined result for the composition
465  if (o.getObservableComposition() != null) {
466  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
467  if (result == null) {
468  result = newResult;
469  } else {
470  result.addResult(newResult, OperatorTypeEnum.AND);
471  }
472  } else {
473  newResult = evaluateSingleObservable(o, spacing + " ");
474  if (result == null) {
475  result = newResult;
476  } else {
477  result.addResult(newResult, OperatorTypeEnum.AND);
478  }
479  }
480 
481  if ((!skipShortCircuit) && !result.isFalse()) {
482  // For testing purposes (and maybe in general), may not want to short-circuit
483  return result;
484  }
485  }
486  // At this point, all comparisions should have been true (or indeterminate)
487  if (result == null) {
488  // This really shouldn't happen, but if we have an empty composition,
489  // indeterminate seems like a reasonable result
490  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
491  }
492 
493  return result;
494 
495  } else {
496  ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
497  for (Observable o : obsList) {
498 
499  ObservableResult newResult;// The combined result for the composition
500 
501  if (o.getObservableComposition() != null) {
502  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
503  if (result == null) {
504  result = newResult;
505  } else {
506  result.addResult(newResult, OperatorTypeEnum.OR);
507  }
508  } else {
509  newResult = evaluateSingleObservable(o, spacing + " ");
510  if (result == null) {
511  result = newResult;
512  } else {
513  result.addResult(newResult, OperatorTypeEnum.OR);
514  }
515  }
516 
517  if ((!skipShortCircuit) && result.isTrue()) {
518  // For testing (and maybe in general), may not want to short-circuit
519  return result;
520  }
521  }
522  // At this point, all comparisions were false (or indeterminate)
523  if (result == null) {
524  // This really shouldn't happen, but if we have an empty composition,
525  // indeterminate seems like a reasonable result
526  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
527  }
528 
529  return result;
530  }
531  } else {
532  throw new TskCoreException("No observables found in list"); //NON-NLS
533  }
534  }
535 
547  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
548 
549  // If we've already calculated this one, return the saved value
550  if (idToResult.containsKey(makeMapKey(obs))) {
551  return idToResult.get(makeMapKey(obs));
552  }
553 
554  if (obs.getIdref() == null) {
555 
556  // We should have the object data right here (as opposed to elsewhere in the STIX file).
557  // Save it to the map.
558  if (obs.getId() != null) {
559  saveToObjectMap(obs);
560  }
561 
562  if (obs.getObject() != null) {
563 
564  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
565  idToResult.put(makeMapKey(obs), result);
566  return result;
567  }
568  }
569 
570  if (idToObjectMap.containsKey(makeMapKey(obs))) {
571  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
572  idToResult.put(makeMapKey(obs), result);
573  return result;
574  }
575 
576  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
577  }
578 
589  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
590 
591  EvaluatableObject evalObj;
592 
593  if (obj.getProperties() instanceof FileObjectType) {
594  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
595  } else if (obj.getProperties() instanceof Address) {
596  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
597  } else if (obj.getProperties() instanceof URIObjectType) {
598  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
599  } else if (obj.getProperties() instanceof EmailMessage) {
600  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
601  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
602  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
603  } else if (obj.getProperties() instanceof AccountObjectType) {
604  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
605  } else if (obj.getProperties() instanceof SystemObjectType) {
606  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
607  } else if (obj.getProperties() instanceof URLHistory) {
608  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
609  } else if (obj.getProperties() instanceof DomainName) {
610  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
611  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
612  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
613  } else {
614  // Try to get the object type as a string
615  String type = obj.getProperties().toString();
616  type = type.substring(0, type.indexOf("@"));
617  if ((type.lastIndexOf(".") + 1) < type.length()) {
618  type = type.substring(type.lastIndexOf(".") + 1);
619  }
620  return new ObservableResult(id, type + " not supported", //NON-NLS
621  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
622  }
623 
624  // Evalutate the object
625  return evalObj.evaluate();
626  }
627 
628  @Override
629  public String getName() {
630  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
631  return name;
632  }
633 
634  @Override
635  public String getRelativeFilePath() {
636  return "stix.txt"; //NON-NLS
637  }
638 
639  @Override
640  public String getDescription() {
641  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
642  return desc;
643  }
644 
645  @Override
646  public JPanel getConfigurationPanel() {
647  initializePanel();
648  return configPanel;
649  }
650 
651  private void initializePanel() {
652  if (configPanel == null) {
653  configPanel = new STIXReportModuleConfigPanel();
654  }
655  }
656 
662  @Override
664  return new STIXReportModuleSettings();
665  }
666 
672  @Override
674  initializePanel();
675  return configPanel.getConfiguration();
676  }
677 
683  @Override
684  public void setConfiguration(ReportModuleSettings settings) {
685  initializePanel();
686  if (settings == null || settings instanceof NoReportModuleSettings) {
687  configPanel.setConfiguration((STIXReportModuleSettings) getDefaultConfiguration());
688  return;
689  }
690 
691  if (settings instanceof STIXReportModuleSettings) {
692  configPanel.setConfiguration((STIXReportModuleSettings) settings);
693  return;
694  }
695 
696  throw new IllegalArgumentException("Expected settings argument to be an instance of STIXReportModuleSettings");
697  }
698 
699 }
void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output)
void saveResultsAsArtifacts(Indicator ind, ObservableResult result, 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:1654
void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel)
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-2021 Basis Technology. Generated on: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.