Autopsy  4.6.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.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<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  // 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
119  NbBundle.getMessage(this.getClass(), "STIXReportModule.notifyErr.noFildDirProvided"));
120  progressPanel.complete(ReportStatus.ERROR);
121  progressPanel.updateStatusLabel(
122  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
123  new File(baseReportDir).delete();
124  return;
125  }
126  if (stixFileName.isEmpty()) {
127  logger.log(Level.SEVERE, "No STIX file/directory provided "); //NON-NLS
129  NbBundle.getMessage(this.getClass(), "STIXReportModule.notifyErr.noFildDirProvided"));
130  progressPanel.complete(ReportStatus.ERROR);
131  progressPanel.updateStatusLabel(
132  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided"));
133  new File(baseReportDir).delete();
134  return;
135  }
136  File stixFile = new File(stixFileName);
137 
138  if (!stixFile.exists()) {
139  logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName)); //NON-NLS
140  MessageNotifyUtil.Message.error(NbBundle.getMessage(this.getClass(),
141  "STIXReportModule.notifyMsg.unableToOpenFileDir",
142  stixFileName));
143  progressPanel.complete(ReportStatus.ERROR);
144  progressPanel.updateStatusLabel(
145  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName));
146  new File(baseReportDir).delete();
147  return;
148  }
149 
150  try (BufferedWriter output = new BufferedWriter(new FileWriter(reportFile))) {
151  // Store the path
152  ModuleSettings.setConfigSetting("STIX", "defaultPath", stixFileName); //NON-NLS
153 
154  // Create array of stix file(s)
155  File[] stixFiles;
156  if (stixFile.isFile()) {
157  stixFiles = new File[1];
158  stixFiles[0] = stixFile;
159  } else {
160  stixFiles = stixFile.listFiles();
161  }
162 
163  // Set the length of the progress bar - we increment twice for each file
164  progressPanel.setMaximumProgress(stixFiles.length * 2 + 1);
165 
166  // Process each STIX file
167  for (File file : stixFiles) {
168  if (progressPanel.getStatus() == ReportStatus.CANCELED) {
169  return;
170  }
171  try {
172  processFile(file.getAbsolutePath(), progressPanel, output);
173  } catch (TskCoreException | JAXBException ex) {
174  String errMsg = String.format("Unable to process STIX file %s", file);
175  logger.log(Level.SEVERE, errMsg, ex); //NON-NLS
176  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
177  errMsg,
179  hadErrors = true;
180  break;
181  }
182  // Clear out the ID maps before loading the next file
183  idToObjectMap = new HashMap<String, ObjectType>();
184  idToResult = new HashMap<String, ObservableResult>();
185  }
186 
187  // Set the progress bar to done. If any errors occurred along the way, modify
188  // the "complete" message to indicate this.
189  Case.getOpenCase().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), "");
190  if (hadErrors) {
191  progressPanel.complete(ReportStatus.ERROR);
192  progressPanel.updateStatusLabel(
193  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
194  } else {
195  progressPanel.complete(ReportStatus.COMPLETE);
196  }
197  } catch (IOException ex) {
198  logger.log(Level.SEVERE, "Unable to complete STIX report.", ex); //NON-NLS
199  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
200  NbBundle.getMessage(this.getClass(),
201  "STIXReportModule.notifyMsg.unableToOpenReportFile"),
203  progressPanel.complete(ReportStatus.ERROR);
204  progressPanel.updateStatusLabel(
205  NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors"));
206  } catch (TskCoreException | NoCurrentCaseException ex) {
207  logger.log(Level.SEVERE, "Unable to add report to database.", ex);
208  }
209  }
210 
221  private void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output) throws
222  JAXBException, TskCoreException {
223 
224  // Load the STIX file
225  STIXPackage stix;
226  stix = loadSTIXFile(stixFile);
227 
228  printFileHeader(stixFile, output);
229 
230  // Save any observables listed up front
231  processObservables(stix);
232  progressPanel.increment();
233 
234  // Make copies of the registry files
235  registryFileData = EvalRegistryObj.copyRegistryFiles();
236 
237  // Process the indicators
238  processIndicators(stix, output);
239  progressPanel.increment();
240 
241  }
242 
252  private STIXPackage loadSTIXFile(String stixFileName) throws JAXBException {
253  // Create STIXPackage object from xml.
254  File file = new File(stixFileName);
255  JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
256  + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
257  Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
258  STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
259  return stix;
260  }
261 
268  private void processObservables(STIXPackage stix) {
269  if (stix.getObservables() != null) {
270  List<Observable> obs = stix.getObservables().getObservables();
271  for (Observable o : obs) {
272  if (o.getId() != null) {
273  saveToObjectMap(o);
274  }
275  }
276  }
277  }
278 
286  private void processIndicators(STIXPackage stix, BufferedWriter output) throws TskCoreException {
287  if (stix.getIndicators() != null) {
288  List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
289  for (IndicatorBaseType t : s) {
290  if (t instanceof Indicator) {
291  Indicator ind = (Indicator) t;
292  if (ind.getObservable() != null) {
293  if (ind.getObservable().getObject() != null) {
294  ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
295  if (result.isTrue() || reportAllResults) {
296  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
297  }
298  if (result.isTrue()) {
299  saveResultsAsArtifacts(ind, result);
300  }
301  } else if (ind.getObservable().getObservableComposition() != null) {
302  ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
303 
304  if (result.isTrue() || reportAllResults) {
305  writeResultsToFile(ind, result.getDescription(), result.isTrue(), output);
306  }
307  if (result.isTrue()) {
308  saveResultsAsArtifacts(ind, result);
309  }
310  }
311  }
312  }
313  }
314  }
315  }
316 
325  private void saveResultsAsArtifacts(Indicator ind, ObservableResult result) throws TskCoreException {
326 
327  if (result.getArtifacts() == null) {
328  return;
329  }
330 
331  // Count of how many artifacts have been created for this indicator.
332  int count = 0;
333 
334  for (StixArtifactData s : result.getArtifacts()) {
335 
336  // Figure out what name to use for this indicator. If it has a title,
337  // use that. Otherwise use the ID. If both are missing, use a
338  // generic heading.
339  if (ind.getTitle() != null) {
340  s.createArtifact(ind.getTitle());
341  } else if (ind.getId() != null) {
342  s.createArtifact(ind.getId().toString());
343  } else {
344  s.createArtifact("Unnamed indicator(s)"); //NON-NLS
345  }
346 
347  // Trying to protect against the case where we end up with tons of artifacts
348  // for a single observable because the condition was not restrictive enough
349  count++;
350  if (count > 1000) {
351  MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
352  NbBundle.getMessage(this.getClass(),
353  "STIXReportModule.notifyMsg.tooManyArtifactsgt1000",
354  ind.getId()),
356  break;
357  }
358  }
359 
360  }
361 
371  private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) {
372  if (output != null) {
373  try {
374  if (found) {
375  output.write("----------------\r\n"
376  + "Found indicator:\r\n"); //NON-NLS
377  } else {
378  output.write("-----------------------\r\n"
379  + "Did not find indicator:\r\n"); //NON-NLS
380  }
381  if (ind.getTitle() != null) {
382  output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS
383  } else {
384  output.write("\r\n");
385  }
386  if (ind.getId() != null) {
387  output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS
388  }
389 
390  if (ind.getDescription() != null) {
391  String desc = ind.getDescription().getValue();
392  desc = desc.trim();
393  output.write("Description: " + desc + "\r\n"); //NON-NLS
394  }
395  output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS
396  } catch (IOException ex) {
397  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
398  }
399  }
400  }
401 
408  private void printFileHeader(String a_fileName, BufferedWriter output) {
409  if (output != null) {
410  try {
411  char[] chars = new char[a_fileName.length() + 8];
412  Arrays.fill(chars, '#');
413  String header = new String(chars);
414  output.write("\r\n" + header);
415  output.write("\r\n");
416  output.write("### " + a_fileName + " ###\r\n");
417  output.write(header + "\r\n\r\n");
418  } catch (IOException ex) {
419  logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS
420  }
421 
422  }
423 
424  }
425 
433  private String makeMapKey(Observable obs) {
434  QName idQ;
435  if (obs.getId() != null) {
436  idQ = obs.getId();
437  } else if (obs.getIdref() != null) {
438  idQ = obs.getIdref();
439  } else {
440  return "";
441  }
442 
443  return idQ.getLocalPart();
444  }
445 
451  private void saveToObjectMap(Observable obs) {
452 
453  if (obs.getObject() != null) {
454  idToObjectMap.put(makeMapKey(obs), obs.getObject());
455  }
456  }
457 
468  private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
469  if (comp.getOperator() == null) {
470  throw new TskCoreException("No operator found in composition"); //NON-NLS
471  }
472 
473  if (comp.getObservables() != null) {
474  List<Observable> obsList = comp.getObservables();
475 
476  // Split based on the type of composition (AND vs OR)
477  if (comp.getOperator() == OperatorTypeEnum.AND) {
478  ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
479  for (Observable o : obsList) {
480 
481  ObservableResult newResult; // The combined result for the composition
482  if (o.getObservableComposition() != null) {
483  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
484  if (result == null) {
485  result = newResult;
486  } else {
487  result.addResult(newResult, OperatorTypeEnum.AND);
488  }
489  } else {
490  newResult = evaluateSingleObservable(o, spacing + " ");
491  if (result == null) {
492  result = newResult;
493  } else {
494  result.addResult(newResult, OperatorTypeEnum.AND);
495  }
496  }
497 
498  if ((!skipShortCircuit) && !result.isFalse()) {
499  // For testing purposes (and maybe in general), may not want to short-circuit
500  return result;
501  }
502  }
503  // At this point, all comparisions should have been true (or indeterminate)
504  if (result == null) {
505  // This really shouldn't happen, but if we have an empty composition,
506  // indeterminate seems like a reasonable result
507  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
508  }
509 
510  return result;
511 
512  } else {
513  ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
514  for (Observable o : obsList) {
515 
516  ObservableResult newResult;// The combined result for the composition
517 
518  if (o.getObservableComposition() != null) {
519  newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
520  if (result == null) {
521  result = newResult;
522  } else {
523  result.addResult(newResult, OperatorTypeEnum.OR);
524  }
525  } else {
526  newResult = evaluateSingleObservable(o, spacing + " ");
527  if (result == null) {
528  result = newResult;
529  } else {
530  result.addResult(newResult, OperatorTypeEnum.OR);
531  }
532  }
533 
534  if ((!skipShortCircuit) && result.isTrue()) {
535  // For testing (and maybe in general), may not want to short-circuit
536  return result;
537  }
538  }
539  // At this point, all comparisions were false (or indeterminate)
540  if (result == null) {
541  // This really shouldn't happen, but if we have an empty composition,
542  // indeterminate seems like a reasonable result
543  return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
544  }
545 
546  return result;
547  }
548  } else {
549  throw new TskCoreException("No observables found in list"); //NON-NLS
550  }
551  }
552 
564  private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
565 
566  // If we've already calculated this one, return the saved value
567  if (idToResult.containsKey(makeMapKey(obs))) {
568  return idToResult.get(makeMapKey(obs));
569  }
570 
571  if (obs.getIdref() == null) {
572 
573  // We should have the object data right here (as opposed to elsewhere in the STIX file).
574  // Save it to the map.
575  if (obs.getId() != null) {
576  saveToObjectMap(obs);
577  }
578 
579  if (obs.getObject() != null) {
580 
581  ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
582  idToResult.put(makeMapKey(obs), result);
583  return result;
584  }
585  }
586 
587  if (idToObjectMap.containsKey(makeMapKey(obs))) {
588  ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
589  idToResult.put(makeMapKey(obs), result);
590  return result;
591  }
592 
593  throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS
594  }
595 
606  private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
607 
608  EvaluatableObject evalObj;
609 
610  if (obj.getProperties() instanceof FileObjectType) {
611  evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
612  } else if (obj.getProperties() instanceof Address) {
613  evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
614  } else if (obj.getProperties() instanceof URIObjectType) {
615  evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
616  } else if (obj.getProperties() instanceof EmailMessage) {
617  evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
618  } else if (obj.getProperties() instanceof WindowsNetworkShare) {
619  evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
620  } else if (obj.getProperties() instanceof AccountObjectType) {
621  evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
622  } else if (obj.getProperties() instanceof SystemObjectType) {
623  evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
624  } else if (obj.getProperties() instanceof URLHistory) {
625  evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
626  } else if (obj.getProperties() instanceof DomainName) {
627  evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing);
628  } else if (obj.getProperties() instanceof WindowsRegistryKey) {
629  evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
630  } else {
631  // Try to get the object type as a string
632  String type = obj.getProperties().toString();
633  type = type.substring(0, type.indexOf("@"));
634  if ((type.lastIndexOf(".") + 1) < type.length()) {
635  type = type.substring(type.lastIndexOf(".") + 1);
636  }
637  return new ObservableResult(id, type + " not supported", //NON-NLS
638  spacing, ObservableResult.ObservableState.INDETERMINATE, null);
639  }
640 
641  // Evalutate the object
642  return evalObj.evaluate();
643  }
644 
645  @Override
646  public String getName() {
647  String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
648  return name;
649  }
650 
651  @Override
652  public String getRelativeFilePath() {
653  return "stix.txt"; //NON-NLS
654  }
655 
656  @Override
657  public String getDescription() {
658  String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
659  return desc;
660  }
661 
662  @Override
663  public JPanel getConfigurationPanel() {
664  configPanel = new STIXReportModuleConfigPanel();
665  return configPanel;
666  }
667 
668 }
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:1549
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:124
static void show(String title, String message, MessageType type, ActionListener actionListener)
void processIndicators(STIXPackage stix, BufferedWriter output)

Copyright © 2012-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.