/* * * Autopsy Forensic Browser * * Copyright 2019 Basis Technology Corp. * * 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.recentactivity; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Scanner; import java.util.logging.Level; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.NetworkUtils; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** * Extract the bookmarks, cookies, downloads and history from the Microsoft Edge */ final class ExtractEdge extends Extract { private static final Logger logger = Logger.getLogger(ExtractEdge.class.getName()); private final IngestServices services = IngestServices.getInstance(); private final Path moduleTempResultPath; private Content dataSource; private IngestJobContext context; private static final String ESE_TOOL_NAME = "ESEDatabaseView.exe"; private static final String EDGE_WEBCACHE_NAME = "WebCacheV01.dat"; private static final String EDGE_WEBCACHE_PREFIX = "WebCacheV01"; private static final String EDGE = "Edge"; private static final String ESE_TOOL_FOLDER = "ESEDatabaseView"; private static final String EDGE_SPARTAN_NAME = "Spartan.edb"; private static final String EDGE_HEAD_URL = "url"; private static final String EDGE_HEAD_ACCESSTIME = "accessedtime"; private static final String EDGE_KEYWORD_VISIT = "Visited:"; private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a"); ExtractEdge() throws NoCurrentCaseException { moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), "results"); } @Messages({ "ExtractEdge_Module_Name=Microsoft Edge" }) @Override protected String getName() { return Bundle.ExtractEdge_Module_Name(); } @Messages({ "ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer", "ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file", "ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file", "ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file" }) @Override void process(Content dataSource, IngestJobContext context) { this.dataSource = dataSource; this.context = context; this.setFoundData(false); List webCacheFiles = null; List spartanFiles = null; try { webCacheFiles = fetchWebCacheFiles(); } catch (TskCoreException ex) { this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles()); logger.log(Level.SEVERE, "Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex); //NON-NLS } try { spartanFiles = fetchSpartanFiles(); // For later use with bookmarks } catch (TskCoreException ex) { this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail()); logger.log(Level.SEVERE, "Error fetching 'spartan.edb' files for Microsoft Edge", ex); //NON-NLS } // No edge files found if (webCacheFiles == null && spartanFiles == null) { return; } this.setFoundData(true); if (!PlatformUtil.isWindowsOS()) { logger.log(Level.WARNING, "Microsoft Edge files found, unable to parse on Non-Windows system"); //NON-NLS return; } final String esedumper = getPathForESEDumper(); if (esedumper == null) { this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer()); logger.log(Level.SEVERE, "Error finding ESEDatabaseViewer program"); //NON-NLS return; //If we cannot find the ESEDatabaseView we cannot proceed } if (context.dataSourceIngestIsCancelled()) { return; } try { this.processWebCache(esedumper, webCacheFiles); } catch (IOException ex) { this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail()); logger.log(Level.SEVERE, "Error returned from processWebCach", ex); // NON-NLS } catch (TskCoreException tcex) { } if (context.dataSourceIngestIsCancelled()) { return; } // Bookmarks come from spartan.edb different file this.getBookmark(); // Not implemented yet } void processWebCache(String eseDumperPath, List webCacheFiles) throws IOException, TskCoreException { for (AbstractFile webCacheFile : webCacheFiles) { if (context.dataSourceIngestIsCancelled()) { return; } //Run the dumper String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX + Integer.toString((int) webCacheFile.getId()) + ".dat"; //NON-NLS File tempWebCacheFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName); try { ContentUtils.writeToFile(webCacheFile, tempWebCacheFile, context::dataSourceIngestIsCancelled); } catch (IOException ex) { throw new IOException("Error writingToFile: " + webCacheFile, ex); //NON-NLS } File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) webCacheFile.getId())); resultsDir.mkdirs(); try { executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(), EDGE_WEBCACHE_PREFIX, resultsDir.getAbsolutePath()); if (context.dataSourceIngestIsCancelled()) { return; } this.getHistory(webCacheFile, resultsDir); // Not implemented yet if (context.dataSourceIngestIsCancelled()) { return; } this.getCookie(); // Not implemented yet if (context.dataSourceIngestIsCancelled()) { return; } this.getDownload(); // Not implemented yet } finally { tempWebCacheFile.delete(); resultsDir.delete(); } } } /** * getHistory searches the files with "container" in the file name for lines * with the text "Visited" in them. Note that not all of the container * files, if fact most of them do not, have the browser history in them. */ @Messages({ "ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history" }) private void getHistory(AbstractFile origFile, File resultDir) throws TskCoreException { File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains("container")); if (containerFiles == null) { this.addErrorMessage(Bundle.ExtractEdge_getHistory_containerFileNotFound()); return; } for (File file : containerFiles) { Scanner fileScanner; try { fileScanner = new Scanner(new FileInputStream(file.toString())); } catch (FileNotFoundException ex) { logger.log(Level.WARNING, "Unable to find the ESEDatabaseView file at " + file.getPath(), ex); //NON-NLS continue; // If we couldn't open this file, continue to the next file } Collection bbartifacts = new ArrayList<>(); try { List headers = null; while (fileScanner.hasNext()) { String line = fileScanner.nextLine(); if (headers == null) { headers = Arrays.asList(line.toLowerCase().split(",")); continue; } if (line.contains(EDGE_KEYWORD_VISIT)) { BlackboardArtifact b = getHistoryArtifact(origFile, headers, line); if (b != null) { bbartifacts.add(b); this.indexArtifact(b); } } else { // If Visited is not in line than this is probably // not the container file we're looking for, move on break; } } } finally { fileScanner.close(); } if (!bbartifacts.isEmpty()) { services.fireModuleDataEvent(new ModuleDataEvent( RecentActivityExtracterModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts)); } } } /** * Search for bookmark files and make artifacts. */ private void getBookmark() { } /** * Queries for cookie files and adds artifacts */ private void getCookie() { } /** * Queries for download files and adds artifacts */ private void getDownload() { } private String getPathForESEDumper() { Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME); File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(), ExtractEdge.class.getPackage().getName(), false); if (eseToolFile != null) { return eseToolFile.getAbsolutePath(); } return null; } private List fetchWebCacheFiles() throws TskCoreException { org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = currentCase.getServices().getFileManager(); return fileManager.findFiles(dataSource, EDGE_WEBCACHE_NAME); } private List fetchSpartanFiles() throws TskCoreException { org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = currentCase.getServices().getFileManager(); return fileManager.findFiles(dataSource, EDGE_SPARTAN_NAME); } private void executeDumper(String dumperPath, String inputFilePath, String inputFilePrefix, String outputDir) throws IOException { final String outputFileFullPath = outputDir + File.separator + inputFilePrefix + ".txt"; //NON-NLS final String errFileFullPath = outputDir + File.separator + inputFilePrefix + ".err"; //NON-NLS logger.log(Level.INFO, "Writing ESEDatabaseViewer results to: {0}", outputDir); //NON-NLS List commandLine = new ArrayList<>(); commandLine.add(dumperPath); commandLine.add("/table"); commandLine.add(inputFilePath); commandLine.add("*"); commandLine.add("/scomma"); commandLine.add(outputDir + "\\" + inputFilePrefix + "_*.csv"); ProcessBuilder processBuilder = new ProcessBuilder(commandLine); processBuilder.redirectOutput(new File(outputFileFullPath)); processBuilder.redirectError(new File(errFileFullPath)); ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context)); } @Messages({ "ExtractEdge_programName=Microsoft Edge" }) private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List headers, String line) throws TskCoreException { String[] rowSplit = line.split(","); int index = headers.indexOf(EDGE_HEAD_URL); String urlUserStr = rowSplit[index]; String[] str = urlUserStr.split("@"); String user = (str[0].replace(EDGE_KEYWORD_VISIT, "")).trim(); String url = str[1]; index = headers.indexOf(EDGE_HEAD_ACCESSTIME); String accessTime = rowSplit[index].trim(); Long ftime = null; try { Long epochtime = DATE_FORMATTER.parse(accessTime).getTime(); ftime = epochtime / 1000; } catch (ParseException ex) { logger.log(Level.WARNING, "The Accessed Time format in history file seems invalid " + accessTime, ex); //NON-NLS } BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY); bbart.addAttributes(createHistoryAttribute(url, ftime, null, null, Bundle.ExtractEdge_programName(), NetworkUtils.extractDomain(url), user)); return bbart; } private Collection createHistoryAttribute(String url, Long accessTime, String referrer, String title, String programName, String domain, String user) throws TskCoreException { Collection bbattributes = new ArrayList<>(); bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL, RecentActivityExtracterModuleFactory.getModuleName(), (url != null) ? url : "")); if (accessTime != null) { bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, RecentActivityExtracterModuleFactory.getModuleName(), accessTime)); } bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REFERRER, RecentActivityExtracterModuleFactory.getModuleName(), (referrer != null) ? referrer : "")); bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE, RecentActivityExtracterModuleFactory.getModuleName(), (title != null) ? title : "")); bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, RecentActivityExtracterModuleFactory.getModuleName(), (programName != null) ? programName : "")); bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, RecentActivityExtracterModuleFactory.getModuleName(), (domain != null) ? domain : "")); bbattributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME, RecentActivityExtracterModuleFactory.getModuleName(), (user != null) ? user : "")); return bbattributes; } }