402 lines
16 KiB
Java
Executable File

/*
*
* 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<AbstractFile> webCacheFiles = null;
List<AbstractFile> 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<AbstractFile> 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<BlackboardArtifact> bbartifacts = new ArrayList<>();
try {
List<String> 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<AbstractFile> fetchWebCacheFiles() throws TskCoreException {
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager
= currentCase.getServices().getFileManager();
return fileManager.findFiles(dataSource, EDGE_WEBCACHE_NAME);
}
private List<AbstractFile> 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<String> 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<String> 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<BlackboardAttribute> createHistoryAttribute(String url, Long accessTime,
String referrer, String title, String programName, String domain, String user) throws TskCoreException {
Collection<BlackboardAttribute> 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;
}
}