Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
VMExtractorIngestModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-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.vmextractor;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.text.SimpleDateFormat;
27 import java.util.ArrayList;
28 import java.util.Calendar;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.UUID;
32 import java.util.logging.Level;
33 import org.openide.util.NbBundle;
34 import org.openide.util.NbBundle.Messages;
53 import org.sleuthkit.datamodel.AbstractFile;
54 import org.sleuthkit.datamodel.Content;
55 import org.sleuthkit.datamodel.DataSource;
56 import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
57 import org.sleuthkit.datamodel.SleuthkitCase;
58 import org.sleuthkit.datamodel.TskCoreException;
59 import org.sleuthkit.datamodel.TskDataException;
60 
65 @NbBundle.Messages({"# {0} - output directory name", "VMExtractorIngestModule.cannotCreateOutputDir.message=Unable to create output directory: {0}."
66 })
67 final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
68 
69  private static final Logger logger = Logger.getLogger(VMExtractorIngestModule.class.getName());
70  private IngestJobContext context;
71  private Path ingestJobOutputDir;
72  private String parentDeviceId;
73  private String parentTimeZone;
74 
75  private final HashMap<String, String> imageFolderToOutputFolder = new HashMap<>();
76  private int folderId = 0;
77 
78  @Messages({"# {0} - data source name", "deviceIdQueryErrMsg=Data source {0} missing Device ID",
79  "VMExtractorIngestModule.noOpenCase.errMsg=No open case available."})
80  @Override
81  public void startUp(IngestJobContext context) throws IngestModuleException {
82  this.context = context;
83  long dataSourceObjId = context.getDataSource().getId();
84  try {
85  Case currentCase = Case.getCurrentCaseThrows();
86  SleuthkitCase caseDb = currentCase.getSleuthkitCase();
87  DataSource dataSource = caseDb.getDataSource(dataSourceObjId);
88  parentDeviceId = dataSource.getDeviceId();
89  parentTimeZone = dataSource.getTimeZone();
90  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
91  String timeStamp = dateFormat.format(Calendar.getInstance().getTime());
92  String ingestJobOutputDirName = context.getDataSource().getName() + "_" + context.getDataSource().getId() + "_" + timeStamp;
93  ingestJobOutputDirName = ingestJobOutputDirName.replace(':', '_');
94  ingestJobOutputDir = Paths.get(currentCase.getModuleDirectory(), VMExtractorIngestModuleFactory.getModuleName(), ingestJobOutputDirName);
95  // create module output folder to write extracted virtual machine files to
96  Files.createDirectories(ingestJobOutputDir);
97  } catch (IOException | SecurityException | UnsupportedOperationException ex) {
98  throw new IngestModule.IngestModuleException(Bundle.VMExtractorIngestModule_cannotCreateOutputDir_message(ex.getLocalizedMessage()), ex);
99  } catch (TskDataException | TskCoreException ex) {
100  throw new IngestModule.IngestModuleException(Bundle.deviceIdQueryErrMsg(context.getDataSource().getName()), ex);
101  } catch (NoCurrentCaseException ex) {
102  throw new IngestModule.IngestModuleException(Bundle.VMExtractorIngestModule_noOpenCase_errMsg(), ex);
103  }
104  }
105 
106  @Override
107  public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
108 
109  String outputFolderForThisVM;
110  List<AbstractFile> vmFiles;
111 
112  // Configure and start progress bar - looking for VM files
113  progressBar.progress(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.searchingImage.message"));
114  // Not sure how long it will take for search to complete.
115  progressBar.switchToIndeterminate();
116 
117  logger.log(Level.INFO, "Looking for virtual machine files in data source {0}", dataSource.getName()); //NON-NLS
118 
119  try {
120  // look for all VM files
121  vmFiles = findVirtualMachineFiles(dataSource);
122  vmFiles = removeNonVMFiles(vmFiles);
123  } catch (TskCoreException ex) {
124  logger.log(Level.SEVERE, "Error querying case database", ex); //NON-NLS
125  return ProcessResult.ERROR;
126  } catch (NoCurrentCaseException ex) {
127  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
128  return ProcessResult.ERROR;
129  }
130 
131  if (vmFiles.isEmpty()) {
132  // no VM files found
133  logger.log(Level.INFO, "No virtual machine files found in data source {0}", dataSource.getName()); //NON-NLS
134  return ProcessResult.OK;
135  }
136  // display progress for saving each VM file to disk
137  progressBar.switchToDeterminate(vmFiles.size());
138  progressBar.progress(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.exportingToDisk.message"));
139 
140  int numFilesSaved = 0;
141  for (AbstractFile vmFile : vmFiles) {
142  if (context.dataSourceIngestIsCancelled()) {
143  break;
144  }
145 
146  logger.log(Level.INFO, "Saving virtual machine file {0} to disk", vmFile.getName()); //NON-NLS
147 
148  // get vmFolderPathInsideTheImage to the folder where VM is located
149  String vmFolderPathInsideTheImage = vmFile.getParentPath();
150 
151  // check if the vmFolderPathInsideTheImage is already in hashmap
152  if (imageFolderToOutputFolder.containsKey(vmFolderPathInsideTheImage)) {
153  // if it is then we have already created output folder to write out all VM files in this parent folder
154  outputFolderForThisVM = imageFolderToOutputFolder.get(vmFolderPathInsideTheImage);
155  } else {
156  // if not - create output folder to write out VM files (can use any unique ID or number for folder name)
157  folderId++;
158  outputFolderForThisVM = Paths.get(ingestJobOutputDir.toString(), Integer.toString(folderId)).toString();
159 
160  // add vmFolderPathInsideTheImage to hashmap
161  imageFolderToOutputFolder.put(vmFolderPathInsideTheImage, outputFolderForThisVM);
162  }
163 
164  // write the vm file to output folder
165  try {
166  writeVirtualMachineToDisk(vmFile, outputFolderForThisVM);
167  } catch (ReadContentInputStreamException ex) {
168  logger.log(Level.WARNING, String.format("Failed to read virtual machine file '%s' (id=%d).",
169  vmFile.getName(), vmFile.getId()), ex); //NON-NLS
170  MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedExtractVM.title.txt"),
171  NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedExtractVM.msg.txt", vmFile.getName()));
172  } catch (Exception ex) {
173  logger.log(Level.SEVERE, String.format("Failed to write virtual machine file '%s' (id=%d) to folder '%s'.",
174  vmFile.getName(), vmFile.getId(), outputFolderForThisVM), ex); //NON-NLS
175  MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedExtractVM.title.txt"),
176  NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedExtractVM.msg.txt", vmFile.getName()));
177  }
178 
179  // Update progress bar
180  numFilesSaved++;
181  progressBar.progress(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.exportingToDisk.message"), numFilesSaved);
182  }
183  logger.log(Level.INFO, "Finished saving virtual machine files to disk"); //NON-NLS
184 
185  // update progress bar
186  progressBar.switchToDeterminate(imageFolderToOutputFolder.size());
187  progressBar.progress(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.queuingIngestJobs.message"));
188  // this is for progress bar purposes because at this point we only know in advance how many job folders to ingest, not how many data sources.
189  int numJobsQueued = 0;
190  // start processing output folders after we are done writing out all vm files
191  for (String folder : imageFolderToOutputFolder.values()) {
192  if (context.dataSourceIngestIsCancelled()) {
193  break;
194  }
195  List<String> vmFilesToIngest = VirtualMachineFinder.identifyVirtualMachines(Paths.get(folder));
196  for (String file : vmFilesToIngest) {
197  try {
198  logger.log(Level.INFO, "Ingesting virtual machine file {0} in folder {1}", new Object[]{file, folder}); //NON-NLS
199 
200  // for extracted virtual machines there is no manifest XML file to read data source ID from so use parent data source ID.
201  // ingest the data sources
202  ingestVirtualMachineImage(Paths.get(folder, file));
203  } catch (InterruptedException ex) {
204  logger.log(Level.INFO, "Interrupted while ingesting virtual machine file " + file + " in folder " + folder, ex); //NON-NLS
205  } catch (IOException ex) {
206  logger.log(Level.SEVERE, "Failed to ingest virtual machine file " + file + " in folder " + folder, ex); //NON-NLS
207  MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedIngestVM.title.txt"),
208  NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedIngestVM.msg.txt", file));
209  } catch (NoCurrentCaseException ex) {
210  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
211  MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.msgNotify.failedIngestVM.title.txt"),
212  Bundle.VMExtractorIngestModule_noOpenCase_errMsg());
213  }
214  }
215  // Update progress bar
216  numJobsQueued++;
217  progressBar.progress(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.queuingIngestJobs.message"), numJobsQueued);
218  }
219  logger.log(Level.INFO, "VMExtractorIngestModule completed processing of data source {0}", dataSource.getName()); //NON-NLS
220  return ProcessResult.OK;
221  }
222 
234  private static List<AbstractFile> findVirtualMachineFiles(Content dataSource) throws TskCoreException, NoCurrentCaseException {
235  List<AbstractFile> vmFiles = new ArrayList<>();
236  for (String vmExtension : GeneralFilter.VIRTUAL_MACHINE_EXTS) {
237  String searchString = "%" + vmExtension; // want a search string that looks like this "%.vmdk"
238  vmFiles.addAll(Case.getCurrentCaseThrows().getServices().getFileManager().findFiles(dataSource, searchString));
239  }
240  return vmFiles;
241  }
242 
251  private static List<AbstractFile> removeNonVMFiles(List<AbstractFile> vmFiles) {
252  List<AbstractFile> vFile = new ArrayList<>();
253  FileTypeDetector fileTypeDetector = null;
254  for (AbstractFile vmFile : vmFiles) {
255  if (vmFile.getNameExtension().equalsIgnoreCase("vhd")) {
256  String fileMimeType = vmFile.getMIMEType();
257  if (fileMimeType == null) {
258  try {
259  fileTypeDetector = new FileTypeDetector();
260  } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
261  logger.log(Level.WARNING, String.format("Unable to create file type detector for determining MIME type for file %s with id of %d", vmFile.getName(), vmFile.getId()));
262  vFile.add(vmFile);
263  continue;
264  }
265  fileMimeType = fileTypeDetector.getMIMEType(vmFile);
266  try {
267  vmFile.setMIMEType(fileMimeType);
268  vmFile.save();
269  } catch (TskCoreException ex) {
270  logger.log(Level.WARNING, String.format("Unable to save mimetype of %s for file %s with id of %d", fileMimeType, vmFile.getName(), vmFile.getId()));
271  }
272  }
273  if (fileMimeType.equalsIgnoreCase("application/x-vhd")) {
274  vFile.add(vmFile);
275  }
276  } else {
277  vFile.add(vmFile);
278  }
279  }
280 
281  return vFile;
282  }
283 
293  private void writeVirtualMachineToDisk(AbstractFile vmFile, String outputFolderForThisVM) throws ReadContentInputStreamException, IOException {
294 
295  // TODO: check available disk space first? See IngestMonitor.getFreeSpace()
296  // check if output folder exists
297  File destinationFolder = Paths.get(outputFolderForThisVM).toFile();
298  if (!destinationFolder.exists()) {
299  destinationFolder.mkdirs();
300  }
301  /*
302  * Write the virtual machine file to disk.
303  */
304  File localFile = Paths.get(outputFolderForThisVM, vmFile.getName()).toFile();
305  ContentUtils.writeToFile(vmFile, localFile, context::dataSourceIngestIsCancelled);
306  }
307 
314  private void ingestVirtualMachineImage(Path vmFile) throws InterruptedException, IOException, NoCurrentCaseException {
315 
316  /*
317  * Try to add the virtual machine file to the case as a data source.
318  */
319  UUID taskId = UUID.randomUUID();
320  Case.getCurrentCaseThrows().notifyAddingDataSource(taskId);
321  ImageDSProcessor dataSourceProcessor = new ImageDSProcessor();
322  AddDataSourceCallback dspCallback = new AddDataSourceCallback(vmFile);
323  synchronized (this) {
324  dataSourceProcessor.run(parentDeviceId, vmFile.toString(), parentTimeZone, false, new AddDataSourceProgressMonitor(), dspCallback);
325  /*
326  * Block the ingest thread until the data source processor finishes.
327  */
328  this.wait();
329  }
330 
331  /*
332  * If the image was added, start analysis on it with the ingest modules for this
333  * ingest context. Note that this does not wait for ingest to complete.
334  */
335  if (!dspCallback.vmDataSources.isEmpty()) {
336  Case.getCurrentCaseThrows().notifyDataSourceAdded(dspCallback.vmDataSources.get(0), taskId);
337  List<Content> dataSourceContent = new ArrayList<>(dspCallback.vmDataSources);
338  IngestJobSettings ingestJobSettings = new IngestJobSettings(context.getExecutionContext());
339  for (String warning : ingestJobSettings.getWarnings()) {
340  logger.log(Level.WARNING, String.format("Ingest job settings warning for virtual machine file %s : %s", vmFile.toString(), warning)); //NON-NLS
341  }
342  IngestServices.getInstance().postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO,
343  VMExtractorIngestModuleFactory.getModuleName(),
344  NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.addedVirtualMachineImage.message", vmFile.toString())));
345  IngestManager.getInstance().beginIngestJob(dataSourceContent, ingestJobSettings);
346  } else {
347  Case.getCurrentCaseThrows().notifyFailedAddingDataSource(taskId);
348  }
349  }
350 
354  private static final class AddDataSourceProgressMonitor implements DataSourceProcessorProgressMonitor {
355 
356  @Override
357  public void setIndeterminate(final boolean indeterminate) {
358  }
359 
360  @Override
361  public void setProgress(final int progress) {
362  }
363 
364  @Override
365  public void setProgressText(final String text) {
366  }
367 
368  }
369 
375 
376  private final Path vmFile;
377  private final List<Content> vmDataSources;
378 
384  private AddDataSourceCallback(Path vmFile) {
385  this.vmFile = vmFile;
386  vmDataSources = new ArrayList<>();
387  }
388 
389  @Override
390  public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List<String> errList, List<Content> content) {
391  for (String error : errList) {
392  String logMessage = String.format("Data source processor error for virtual machine file %s: %s", vmFile.toString(), error); //NON-NLS
394  logger.log(Level.SEVERE, logMessage);
395  } else {
396  logger.log(Level.WARNING, logMessage);
397  }
398  }
399 
400  /*
401  * Save a reference to the content object so it can be used to
402  * create a new ingest job.
403  */
404  if (!content.isEmpty()) {
405  vmDataSources.add(content.get(0));
406  }
407 
408  /*
409  * Unblock the ingest thread.
410  */
411  synchronized (VMExtractorIngestModule.this) {
412  VMExtractorIngestModule.this.notify();
413  }
414  }
415 
416  @Override
417  public void doneEDT(DataSourceProcessorResult result, List<String> errList, List<Content> newContents) {
418  done(result, errList, newContents);
419  }
420 
421  }
422 
423 }
void doneEDT(DataSourceProcessorResult result, List< String > errList, List< Content > newContents)
void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List< String > errList, List< Content > content)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.