Autopsy  4.15.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  File file = new File(stixFileName);
232  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
233  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
234  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
235  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
236  return stix;
237  }
238 
245  private void processObservables(STIXPackage stix) {
246  if (stix.getObservables() != null) {
247  List<Observable> obs = stix.getObservables().getObservables();
248  for (Observable o : obs) {
249  if (o.getId() != null) {
250  saveToObjectMap(o);
251  }
252  }
253  }
254  }
255 
264  private void processIndicators(STIXPackage stix, BufferedWriter output, ReportProgressPanel progressPanel) throws TskCoreException {
265  if (stix.getIndicators() != null) {
266  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
267  for (IndicatorBaseType t : s) {
268  if (t instanceof Indicator) {
269  Indicator ind = (Indicator) t;
270  if (ind.getObservable() != null) {
271  if (ind.getObservable().getObject() != null) {
272  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
273  if (result.isTrue() || reportAllResults) {
274  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
275  }
276  if (result.isTrue()) {
277  saveResultsAsArtifacts(ind, result, progressPanel);
278  }
279  } else if (ind.getObservable().getObservableComposition() != null) {
280  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
281 
282  if (result.isTrue() || reportAllResults) {
283  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
284  }
285  if (result.isTrue()) {
286  saveResultsAsArtifacts(ind, result, progressPanel);
287  }
288  }
289  }
290  }
291  }
292  }
293  }
294 
304  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result, ReportProgressPanel progressPanel) throws TskCoreException {
305 
306  if (result.getArtifacts() == null) {
307  return;
308  }
309 
310  // Count of how many artifacts have been created for this indicator.
311  int count = 0;
312 
313  for (StixArtifactData s : result.getArtifacts()) {
314 
315  // Figure out what name to use for this indicator. If it has a title,
316  // use that. Otherwise use the ID. If both are missing, use a
317  // generic heading.
318  if (ind.getTitle() != null) {
319  s.createArtifact(ind.getTitle());
320  } else if (ind.getId() != null) {
321  s.createArtifact(ind.getId().toString());
322  } else {
323  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
324  }
325 
326  // Trying to protect against the case where we end up with tons of artifacts
327  // for a single observable because the condition was not restrictive enough
328  count++;
329  if (count > 1000) {
330  progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(),
331  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000"));
332  break;
333  }
334  }
335 
336  }
337 
347  private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) {
348  if (output != null) {
349  try {
350  if (found) {
351  output.write("----------------\r\n"
352  + "Found indicator:\r\n"); //NON-NLS
353  } else {
354  output.write("-----------------------\r\n"
355  + "Did not find indicator:\r\n"); //NON-NLS
356  }
357  if (ind.getTitle() != null) {
358  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
359  } else {
360  output.write("\r\n");
361  }
362  if (ind.getId() != null) {
363  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
364  }
365 
366  if (ind.getDescription() != null) {
367  String desc = ind.getDescription().getValue();
368  desc = desc.trim();
369  output.write("Description: " + desc + "\r\n"); //NON-NLS
370  }
371  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
372  } catch (IOException ex) {
373  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
374  }
375  }
376  }
377 
384  private void printFileHeader(String a_fileName, BufferedWriter output) {
385  if (output != null) {
386  try {
387  char[] chars = new char[a_fileName.length() + 8];
388  Arrays.fill(chars, '#');
389  String header = new String(chars);
390  output.write("\r\n" + header);
391  output.write("\r\n");
392  output.write("### " + a_fileName + " ###\r\n");
393  output.write(header + "\r\n\r\n");
394  } catch (IOException ex) {
395  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
396  }
397 
398  }
399 
400  }
401 
409  private String makeMapKey(Observable obs) {
410  QName idQ;
411  if (obs.getId() != null) {
412  idQ = obs.getId();
413  } else if (obs.getIdref() != null) {
414  idQ = obs.getIdref();
415  } else {
416  return "";
417  }
418 
419  return idQ.getLocalPart();
420  }
421 
427  private void saveToObjectMap(Observable obs) {
428 
429  if (obs.getObject() != null) {
430  idToObjectMap.put(makeMapKey(obs), obs.getObject());
431  }
432  }
433 
444  private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
445  if (comp.getOperator() == null) {
446  throw new TskCoreException("No operator found in composition"); //NON-NLS
447  }
448 
449  if (comp.getObservables() != null) {
450  List<Observable> obsList = comp.getObservables();
451 
452  // Split based on the type of composition (AND vs OR)
453  if (comp.getOperator() == OperatorTypeEnum.AND) {
454  ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
455  for (Observable o : obsList) {
456 
457  ObservableResult newResult; // The combined result for the composition
458  if (o.getObservableComposition() != null) {
459  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
460  if (result == null) {
461  result = newResult;
462  } else {
463  result.addResult(newResult, OperatorTypeEnum.AND);
464  }
465  } else {
466  newResult = evaluateSingleObservable(o, spacing + " ");
467  if (result == null) {
468  result = newResult;
469  } else {
470  result.addResult(newResult, OperatorTypeEnum.AND);
471  }
472  }
473 
474  if ((!skipShortCircuit) && !result.isFalse()) {
475  // For testing purposes (and maybe in general), may not want to short-circuit
476  return result;
477  }
478  }
479  // At this point, all comparisions should have been true (or indeterminate)
480  if (result == null) {
481  // This really shouldn't happen, but if we have an empty composition,
482  // indeterminate seems like a reasonable result
483  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
484  }
485 
486  return result;
487 
488  } else {
489  ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
490  for (Observable o : obsList) {
491 
492  ObservableResult newResult;// The combined result for the composition
493 
494  if (o.getObservableComposition() != null) {
495  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
496  if (result == null) {
497  result = newResult;
498  } else {
499  result.addResult(newResult, OperatorTypeEnum.OR);
500  }
501  } else {
502  newResult = evaluateSingleObservable(o, spacing + " ");
503  if (result == null) {
504  result = newResult;
505  } else {
506  result.addResult(newResult, OperatorTypeEnum.OR);
507  }
508  }
509 
510  if ((!skipShortCircuit) && result.isTrue()) {
511  // For testing (and maybe in general), may not want to short-circuit
512  return result;
513  }
514  }
515  // At this point, all comparisions were false (or indeterminate)
516  if (result == null) {
517  // This really shouldn't happen, but if we have an empty composition,
518  // indeterminate seems like a reasonable result
519  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
520  }
521 
522  return result;
523  }
524  } else {
525  throw new TskCoreException("No observables found in list"); //NON-NLS
526  }
527  }
528 
540  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
541 
542  // If we've already calculated this one, return the saved value
543  if (idToResult.containsKey(makeMapKey(obs))) {
544  return idToResult.get(makeMapKey(obs));
545  }
546 
547  if (obs.getIdref() == null) {
548 
549  // We should have the object data right here (as opposed to elsewhere in the STIX file).
550  // Save it to the map.
551  if (obs.getId() != null) {
552  saveToObjectMap(obs);
553  }
554 
555  if (obs.getObject() != null) {
556 
557  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
558  idToResult.put(makeMapKey(obs), result);
559  return result;
560  }
561  }
562 
563  if (idToObjectMap.containsKey(makeMapKey(obs))) {
564  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
565  idToResult.put(makeMapKey(obs), result);
566  return result;
567  }
568 
569  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
570  }
571 
582  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
583 
584  EvaluatableObject evalObj;
585 
586  if (obj.getProperties() instanceof FileObjectType) {
587  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
588  } else if (obj.getProperties() instanceof Address) {
589  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
590  } else if (obj.getProperties() instanceof URIObjectType) {
591  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
592  } else if (obj.getProperties() instanceof EmailMessage) {
593  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
594  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
595  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
596  } else if (obj.getProperties() instanceof AccountObjectType) {
597  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
598  } else if (obj.getProperties() instanceof SystemObjectType) {
599  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
600  } else if (obj.getProperties() instanceof URLHistory) {
601  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
602  } else if (obj.getProperties() instanceof DomainName) {
603  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
604  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
605  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
606  } else {
607  // Try to get the object type as a string
608  String type = obj.getProperties().toString();
609  type = type.substring(0, type.indexOf("@"));
610  if ((type.lastIndexOf(".") + 1) < type.length()) {
611  type = type.substring(type.lastIndexOf(".") + 1);
612  }
613  return new ObservableResult(id, type + " not supported", //NON-NLS
614  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
615  }
616 
617  // Evalutate the object
618  return evalObj.evaluate();
619  }
620 
621  @Override
622  public String getName() {
623  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
624  return name;
625  }
626 
627  @Override
628  public String getRelativeFilePath() {
629  return "stix.txt"; //NON-NLS
630  }
631 
632  @Override
633  public String getDescription() {
634  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
635  return desc;
636  }
637 
638  @Override
639  public JPanel getConfigurationPanel() {
640  initializePanel();
641  return configPanel;
642  }
643 
644  private void initializePanel() {
645  if (configPanel == null) {
646  configPanel = new STIXReportModuleConfigPanel();
647  }
648  }
649 
655  @Override
657  return new STIXReportModuleSettings();
658  }
659 
665  @Override
667  initializePanel();
668  return configPanel.getConfiguration();
669  }
670 
676  @Override
677  public void setConfiguration(ReportModuleSettings settings) {
678  initializePanel();
679  if (settings == null || settings instanceof NoReportModuleSettings) {
680  configPanel.setConfiguration((STIXReportModuleSettings) getDefaultConfiguration());
681  return;
682  }
683 
684  if (settings instanceof STIXReportModuleSettings) {
685  configPanel.setConfiguration((STIXReportModuleSettings) settings);
686  return;
687  }
688 
689  throw new IllegalArgumentException("Expected settings argument to be an instance of STIXReportModuleSettings");
690  }
691 
692 }
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:1661
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-2020 Basis Technology. Generated on: Mon Jul 6 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.