/* * Autopsy Forensic Browser * * Copyright 2012-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.modules.vmextractor; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import javax.swing.filechooser.FileFilter; import org.sleuthkit.autopsy.casemodule.GeneralFilter; import org.sleuthkit.autopsy.coreutils.Logger; /** * Virtual machine file finder */ public final class VirtualMachineFinder { private static final Logger logger = Logger.getLogger(VirtualMachineFinder.class.getName()); private static final int MAX_VMDK_DESCRIPTOR_FILE_SIZE_BYTES = 10000; private static final int MIN_VMDK_EXTENT_DESCRIPTOR_FIELDS = 4; // See readExtentFilesFromVmdkDescriptorFile() for details private static final int FILE_NAME_FIELD_INDX = 3; // See readExtentFilesFromVmdkDescriptorFile() for details private static final GeneralFilter virtualMachineFilter = new GeneralFilter(GeneralFilter.VIRTUAL_MACHINE_EXTS, GeneralFilter.VIRTUAL_MACHINE_DESC); private static final List<FileFilter> vmFiltersList = new ArrayList<>(); static { vmFiltersList.add(virtualMachineFilter); } private static final List<String> VMDK_EXTS = Arrays.asList(new String[]{".vmdk"}); //NON-NLS private static final GeneralFilter vmdkFilter = new GeneralFilter(VMDK_EXTS, ""); private static final List<FileFilter> vmdkFiltersList = new ArrayList<>(); static { vmdkFiltersList.add(vmdkFilter); } public static final boolean isVirtualMachine(String fileName) { return isAcceptedByFiler(new File(fileName), vmFiltersList); } /** * Identifies virtual machine files for ingest. * * @param imageFolderPath Absolute path to the folder to be analyzed * * @return List of VM files to be ingested */ public static List<String> identifyVirtualMachines(Path imageFolderPath) { // get a list of all files in the folder List<String> files = getAllFilesInFolder(imageFolderPath.toString()); if (files.isEmpty()) { return Collections.emptyList(); } // remove all non-vm files for (Iterator<String> iterator = files.iterator(); iterator.hasNext();) { String file = iterator.next(); if (!isVirtualMachine(file)) { iterator.remove(); } } // identify VMDK descriptor files - VMDK files with size less than 10KB List<String> extentFiles = new ArrayList<>(); for (String fileName : files) { File file = imageFolderPath.resolve(fileName).toFile(); if (isAcceptedByFiler(new File(fileName), vmdkFiltersList) && file.exists() && file.length() < MAX_VMDK_DESCRIPTOR_FILE_SIZE_BYTES) { // this is likely a VMDK descriptor file - read vmdk extent files listed in it extentFiles.addAll(readExtentFilesFromVmdkDescriptorFile(file)); } } // remove VMDK extent files from list of vm files to proces files.removeAll(extentFiles); // what remains on the list is either a vmdk descriptor file or a VMDK file that doesn't have a descriptor file or different type of VM (e.g. VHD) return files; } /** * Opens VMDK descriptor file, finds and returns a list of all VMDK extent * files listed in the descriptor file. * * @param file VMDK descriptor file to read * * @return List of VMDK extent file names listed in the descriptor file */ private static List<String> readExtentFilesFromVmdkDescriptorFile(File file) { List<String> extentFiles = new ArrayList<>(); // remove from the list all VMDK files that are listed in the descriptor file try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line = br.readLine(); while (null != line) { // The extent descriptions provide the following key information: // Access – may be RW, RDONLY, or NOACCESS // Size in sectors – a sector is 512 bytes // Type of extent – may be FLAT, SPARSE, ZERO, VMFS, VMFSSPARSE, VMFSRDM, or VMFSRAW. // Filename // Offset – the offset value is specified only for flat extents and corresponds to the offset in the file or device // where the guest operating system’s data is located. // Example: RW 4192256 SPARSE "win7-ult-vm-0-s001.vmdk" String[] splited = line.split(" "); if (splited.length < MIN_VMDK_EXTENT_DESCRIPTOR_FIELDS) { // line doesn't have enough fields, can't be an extent descriptor continue; } if (splited[0].equals("RW") || splited[0].equals("RDONLY") || splited[0].equals("NOACCESS")) { //NON-NLS // found an extent descriptor // remove quotation marks around the file name String extentFileName = splited[FILE_NAME_FIELD_INDX].replace("\"", ""); // add extent file to list of extent files extentFiles.add(extentFileName); } line = br.readLine(); } } catch (Exception ex) { logger.log(Level.WARNING, String.format("Error while parsing vmdk descriptor file %s", file.toString()), ex); //NON-NLS } return extentFiles; } private static boolean isAcceptedByFiler(File file, List<FileFilter> filters) { for (FileFilter filter : filters) { if (filter.accept(file)) { return true; } } return false; } /** * Returns a list of all file names in the folder of interest. Sub-folders * are excluded. * * @param path Absolute path of the folder of interest * * @return List of all file names in the folder of interest */ private static List<String> getAllFilesInFolder(String path) { // only returns files, skips folders File file = new File(path); String[] files = file.list((File current, String name) -> new File(current, name).isFile()); if (files == null) { // null is returned when folder doesn't exist. need to check this condition, otherwise there is NullPointerException when converting to List return Collections.emptyList(); } return new ArrayList<>(Arrays.asList(files)); } /** * Prevent instantiation of this utility class. */ private VirtualMachineFinder() { } }