mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
870 lines
34 KiB
Java
Executable File
870 lines
34 KiB
Java
Executable File
/*
|
|
*
|
|
* Autopsy Forensic Browser
|
|
*
|
|
* Copyright 2019-2020 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.HashMap;
|
|
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.FileUtil;
|
|
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.DataSourceIngestModuleProgress;
|
|
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
|
import org.sleuthkit.datamodel.AbstractFile;
|
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
|
import org.sleuthkit.datamodel.Content;
|
|
import org.sleuthkit.datamodel.TskCoreException;
|
|
|
|
/**
|
|
* Extract the bookmarks, cookies, downloads and history from Microsoft Edge
|
|
*/
|
|
final class ExtractEdge extends Extract {
|
|
|
|
private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
|
|
private final Path moduleTempResultPath;
|
|
private Content dataSource;
|
|
private IngestJobContext context;
|
|
private HashMap<String, ArrayList<String>> containersTable;
|
|
|
|
private static final String EDGE = "Edge"; //NON-NLS
|
|
|
|
private static final String EDGE_KEYWORD_VISIT = "Visited:"; //NON-NLS
|
|
private static final String IGNORE_COMMA_IN_QUOTES_REGEX = ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"; //NON-NLS
|
|
|
|
private static final String EDGE_TABLE_TYPE_DOWNLOAD = "iedownload"; //NON-NLS
|
|
private static final String EDGE_TABLE_TYPE_HISTORY = "History"; //NON-NLS
|
|
private static final String EDGE_TABLE_TYPE_COOKIE = "cookie"; //NON-NLS
|
|
|
|
private static final String EDGE_HEAD_URL = "url"; //NON-NLS
|
|
private static final String EDGE_HEAD_ACCESSTIME = "accessedtime"; //NON-NLS
|
|
private static final String EDGE_HEAD_NAME = "name"; //NON-NLS
|
|
private static final String EDGE_HEAD_CONTAINER_ID = "containerid"; //NON-NLS
|
|
private static final String EDGE_HEAD_RESPONSEHEAD = "responseheaders"; //NON-NLS
|
|
private static final String EDGE_HEAD_TITLE = "title"; //NON-NLS
|
|
private static final String EDGE_HEAD_RDOMAIN = "rdomain"; //NON-NLS
|
|
private static final String EDGE_HEAD_VALUE = "value"; //NON-NLS
|
|
private static final String EDGE_HEAD_LASTMOD = "lastmodified"; //NON-NLS
|
|
|
|
private static final String EDGE_WEBCACHE_PREFIX = "WebCacheV01"; //NON-NLS
|
|
private static final String EDGE_CONTAINER_FILE_PREFIX = "Container_"; //NON-NLS
|
|
private static final String EDGE_CONTAINER_FILE_EXT = ".csv"; //NON-NLS
|
|
private static final String EDGE_WEBCACHE_EXT = ".dat"; //NON-NLS
|
|
|
|
private static final String ESE_TOOL_NAME = "ESEDatabaseView.exe"; //NON-NLS
|
|
private static final String EDGE_WEBCACHE_NAME = "WebCacheV01.dat"; //NON-NLS
|
|
private static final String EDGE_SPARTAN_NAME = "Spartan.edb"; //NON-NLS
|
|
private static final String EDGE_CONTAINTERS_FILE_NAME = "Containers.csv"; //NON-NLS
|
|
private static final String EDGE_FAVORITE_FILE_NAME = "Favorites.csv"; //NON-NLS
|
|
private static final String EDGE_OUTPUT_FILE_NAME = "Output.txt"; //NON-NLS
|
|
private static final String EDGE_ERROR_FILE_NAME = "File.txt"; //NON-NLS
|
|
private static final String EDGE_WEBCACHE_FOLDER_NAME = "WebCache"; //NON-NLS
|
|
private static final String EDGE_SPARTAN_FOLDER_NAME = "MicrosoftEdge"; //NON-NLS
|
|
|
|
private static final String ESE_TOOL_FOLDER = "ESEDatabaseView"; //NON-NLS
|
|
private static final String EDGE_RESULT_FOLDER_NAME = "results"; //NON-NLS
|
|
|
|
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a"); //NON-NLS
|
|
|
|
@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",
|
|
"ExtractEdge_Module_Name=Microsoft Edge",
|
|
"ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
|
|
"Progress_Message_Edge_History=Microsoft Edge History",
|
|
"Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks",
|
|
"Progress_Message_Edge_Cookies=Microsoft Edge Cookies",
|
|
})
|
|
|
|
/**
|
|
* Extract the bookmarks, cookies, downloads and history from Microsoft Edge
|
|
*/
|
|
ExtractEdge() throws NoCurrentCaseException {
|
|
moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME);
|
|
}
|
|
|
|
@Override
|
|
protected String getName() {
|
|
return Bundle.ExtractEdge_Module_Name();
|
|
}
|
|
|
|
@Override
|
|
void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
|
|
this.dataSource = dataSource;
|
|
this.context = context;
|
|
this.setFoundData(false);
|
|
|
|
List<AbstractFile> webCacheFiles = null;
|
|
List<AbstractFile> spartanFiles = null;
|
|
|
|
try {
|
|
webCacheFiles = fetchWebCacheDBFiles();
|
|
} catch (TskCoreException ex) {
|
|
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
|
|
LOG.log(Level.SEVERE, "Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex); //NON-NLS
|
|
}
|
|
|
|
try {
|
|
spartanFiles = fetchSpartanDBFiles(); // For later use with bookmarks
|
|
} catch (TskCoreException ex) {
|
|
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
|
|
LOG.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()) {
|
|
LOG.log(Level.WARNING, "Microsoft Edge files found, unable to parse on Non-Windows system"); //NON-NLS
|
|
return;
|
|
}
|
|
|
|
final String esedumper = getPathForESEDumper();
|
|
if (esedumper == null) {
|
|
LOG.log(Level.SEVERE, "Error finding ESEDatabaseViewer program"); //NON-NLS
|
|
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
|
|
return; //If we cannot find the ESEDatabaseView we cannot proceed
|
|
}
|
|
|
|
try {
|
|
this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar);
|
|
} catch (IOException | TskCoreException ex) {
|
|
LOG.log(Level.SEVERE, "Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex); // NON-NLS
|
|
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
|
|
}
|
|
|
|
progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
|
|
try {
|
|
this.processSpartanDbFile(esedumper, spartanFiles);
|
|
} catch (IOException | TskCoreException ex) {
|
|
LOG.log(Level.SEVERE, "Error processing 'spartan.edb' files for Microsoft Edge", ex); // NON-NLS
|
|
this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process WebCacheV01.dat ese database file creating artifacts for cookies,
|
|
* and history contained within.
|
|
*
|
|
* @param eseDumperPath Path to ESEDatabaseView.exe
|
|
* @param webCacheFiles List of case WebCacheV01.dat files
|
|
* @throws IOException
|
|
* @throws TskCoreException
|
|
*/
|
|
void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar) throws IOException, TskCoreException {
|
|
|
|
for (AbstractFile webCacheFile : webCacheFiles) {
|
|
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
clearContainerTable();
|
|
|
|
//Run the dumper
|
|
String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
|
|
+ Integer.toString((int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; //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(),
|
|
resultsDir.getAbsolutePath());
|
|
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
progressBar.progress(Bundle.Progress_Message_Edge_History());
|
|
|
|
this.getHistory(webCacheFile, resultsDir);
|
|
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
progressBar.progress(Bundle.Progress_Message_Edge_Cookies());
|
|
|
|
this.getCookies(webCacheFile, resultsDir);
|
|
|
|
} finally {
|
|
tempWebCacheFile.delete();
|
|
FileUtil.deleteFileDir(resultsDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process spartan.edb ese database file creating artifacts for the bookmarks
|
|
* contained within.
|
|
*
|
|
* @param eseDumperPath Path to ESEDatabaseViewer
|
|
* @param spartanFiles List of the case spartan.edb files
|
|
* @throws IOException
|
|
* @throws TskCoreException
|
|
*/
|
|
void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles) throws IOException, TskCoreException {
|
|
|
|
for (AbstractFile spartanFile : spartanFiles) {
|
|
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
//Run the dumper
|
|
String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
|
|
+ Integer.toString((int) spartanFile.getId()) + EDGE_WEBCACHE_EXT;
|
|
File tempSpartanFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName);
|
|
|
|
try {
|
|
ContentUtils.writeToFile(spartanFile, tempSpartanFile,
|
|
context::dataSourceIngestIsCancelled);
|
|
} catch (IOException ex) {
|
|
throw new IOException("Error writingToFile: " + spartanFile, ex); //NON-NLS
|
|
}
|
|
|
|
File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) spartanFile.getId()));
|
|
resultsDir.mkdirs();
|
|
try {
|
|
executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
|
|
resultsDir.getAbsolutePath());
|
|
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
this.getBookmarks(spartanFile, resultsDir);
|
|
|
|
} finally {
|
|
tempSpartanFile.delete();
|
|
FileUtil.deleteFileDir(resultsDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param origFile Original case file
|
|
* @param resultDir Output directory of ESEDatabaseViewer
|
|
* @throws TskCoreException
|
|
* @throws FileNotFoundException
|
|
*/
|
|
private void getHistory(AbstractFile origFile, File resultDir) throws TskCoreException, FileNotFoundException {
|
|
ArrayList<File> historyFiles = getHistoryFiles(resultDir);
|
|
|
|
if (historyFiles == null) {
|
|
return;
|
|
}
|
|
|
|
for (File file : historyFiles) {
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
Scanner fileScanner;
|
|
try {
|
|
fileScanner = new Scanner(new FileInputStream(file.toString()));
|
|
} catch (FileNotFoundException ex) {
|
|
LOG.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()) {
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
String line = fileScanner.nextLine();
|
|
if (headers == null) {
|
|
headers = Arrays.asList(line.toLowerCase().split(","));
|
|
continue;
|
|
}
|
|
|
|
if (line.contains(EDGE_KEYWORD_VISIT)) {
|
|
BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
|
|
if (ba != null) {
|
|
bbartifacts.add(ba);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
fileScanner.close();
|
|
}
|
|
|
|
if (!bbartifacts.isEmpty()) {
|
|
postArtifacts(bbartifacts);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search for bookmark files and make artifacts.
|
|
*
|
|
* @param origFile Original case file
|
|
* @param resultDir Output directory of ESEDatabaseViewer
|
|
* @throws TskCoreException
|
|
* @throws FileNotFoundException
|
|
*/
|
|
private void getBookmarks(AbstractFile origFile, File resultDir) throws TskCoreException {
|
|
Scanner fileScanner;
|
|
File favoriteFile = new File(resultDir, EDGE_FAVORITE_FILE_NAME);
|
|
|
|
try {
|
|
fileScanner = new Scanner(new FileInputStream(favoriteFile));
|
|
} catch (FileNotFoundException ex) {
|
|
// This is a non-fatal error, if the favorites file is not found
|
|
// there might have not been any favorites\bookmarks
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
|
|
if (ba != null) {
|
|
bbartifacts.add(ba);
|
|
}
|
|
}
|
|
} finally {
|
|
fileScanner.close();
|
|
}
|
|
|
|
if (!bbartifacts.isEmpty()) {
|
|
postArtifacts(bbartifacts);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queries for cookie files and adds artifacts.
|
|
*
|
|
* @param origFile Original case file
|
|
* @param resultDir Output directory of ESEDatabaseViewer
|
|
* @throws TskCoreException
|
|
*/
|
|
private void getCookies(AbstractFile origFile, File resultDir) throws TskCoreException {
|
|
File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
|
|
|
|
if (containerFiles == null) {
|
|
return;
|
|
}
|
|
|
|
for (File file : containerFiles) {
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
Scanner fileScanner;
|
|
try {
|
|
fileScanner = new Scanner(new FileInputStream(file.toString()));
|
|
} catch (FileNotFoundException ex) {
|
|
LOG.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()) {
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
String line = fileScanner.nextLine();
|
|
if (headers == null) {
|
|
headers = Arrays.asList(line.toLowerCase().split(","));
|
|
continue;
|
|
}
|
|
|
|
BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
|
|
if (ba != null) {
|
|
bbartifacts.add(ba);
|
|
}
|
|
}
|
|
} finally {
|
|
fileScanner.close();
|
|
}
|
|
|
|
if (!bbartifacts.isEmpty()) {
|
|
postArtifacts(bbartifacts);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queries for download files and adds artifacts.
|
|
*
|
|
* Leaving for future use.
|
|
*
|
|
* @param origFile Original case file
|
|
* @param resultDir Output directory of ESEDatabaseViewer
|
|
* @throws TskCoreException
|
|
* @throws FileNotFoundException
|
|
*/
|
|
private void getDownloads(AbstractFile origFile, File resultDir) throws TskCoreException, FileNotFoundException {
|
|
ArrayList<File> downloadFiles = getDownloadFiles(resultDir);
|
|
|
|
if (downloadFiles == null) {
|
|
return;
|
|
}
|
|
|
|
for (File file : downloadFiles) {
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
Scanner fileScanner;
|
|
try {
|
|
fileScanner = new Scanner(new FileInputStream(file.toString()));
|
|
} catch (FileNotFoundException ex) {
|
|
LOG.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()) {
|
|
if (context.dataSourceIngestIsCancelled()) {
|
|
return;
|
|
}
|
|
|
|
String line = fileScanner.nextLine();
|
|
if (headers == null) {
|
|
headers = Arrays.asList(line.toLowerCase().split(","));
|
|
continue;
|
|
}
|
|
|
|
if (line.contains(EDGE_TABLE_TYPE_DOWNLOAD)) {
|
|
|
|
BlackboardArtifact ba = getDownloadArtifact(origFile, headers, line);
|
|
if (ba != null) {
|
|
bbartifacts.add(ba);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
fileScanner.close();
|
|
}
|
|
|
|
postArtifacts(bbartifacts);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the location of ESEDatabaseViewer.exe
|
|
*
|
|
* @return Absolute path to ESEDatabaseViewer.exe or null if the file is not found
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Finds all of the WebCacheV01.dat files in the case
|
|
*
|
|
* @return A list of WebCacheV01.dat files, possibly empty if none are found
|
|
* @throws TskCoreException
|
|
*/
|
|
private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
|
|
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager
|
|
= currentCase.getServices().getFileManager();
|
|
return fileManager.findFiles(dataSource, EDGE_WEBCACHE_NAME, EDGE_WEBCACHE_FOLDER_NAME);
|
|
}
|
|
|
|
/**
|
|
* Finds all of the spartan.edb files in the case
|
|
*
|
|
* @return A list of spartan files, possibly empty if none are found
|
|
* @throws TskCoreException
|
|
*/
|
|
private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
|
|
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager
|
|
= currentCase.getServices().getFileManager();
|
|
return fileManager.findFiles(dataSource, EDGE_SPARTAN_NAME, EDGE_SPARTAN_FOLDER_NAME);
|
|
}
|
|
|
|
/**
|
|
* Executes the ESEViewDumper on the given inputFile.
|
|
*
|
|
* Each table in the ese database will be dumped as a comma separated file
|
|
* named <tableName>.csv
|
|
*
|
|
* @param dumperPath Path to ESEDatabaseView.exe
|
|
* @param inputFilePath Path to ese database file to be dumped
|
|
* @param outputDir Output directory for dumper
|
|
* @throws IOException
|
|
*/
|
|
private void executeDumper(String dumperPath, String inputFilePath,
|
|
String outputDir) throws IOException {
|
|
|
|
final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
|
|
final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
|
|
LOG.log(Level.INFO, "Writing ESEDatabaseViewer results to: {0}", outputDir); //NON-NLS
|
|
|
|
List<String> commandLine = new ArrayList<>();
|
|
commandLine.add(dumperPath);
|
|
commandLine.add("/table"); //NON-NLS
|
|
commandLine.add(inputFilePath);
|
|
commandLine.add("*"); //NON-NLS
|
|
commandLine.add("/scomma"); //NON-NLS
|
|
commandLine.add(outputDir + "\\" + "*.csv"); //NON-NLS
|
|
|
|
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
|
|
processBuilder.redirectOutput(outputFilePath.toFile());
|
|
processBuilder.redirectError(errFilePath.toFile());
|
|
|
|
ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context, true));
|
|
}
|
|
|
|
/**
|
|
* Create a BlackboardArtifact for the given row from the Edge history
|
|
* table.
|
|
*
|
|
* @param origFile Original case file
|
|
* @param headers List of table headers
|
|
* @param line CSV string representing a row of history table
|
|
* @return BlackboardArtifact representing one history table entry
|
|
* @throws TskCoreException
|
|
*/
|
|
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) {
|
|
LOG.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,
|
|
this.getName(),
|
|
NetworkUtils.extractDomain(url), user));
|
|
|
|
return bbart;
|
|
}
|
|
|
|
/**
|
|
* Create a BlackboardArtifact for the given row from the Edge cookie table.
|
|
*
|
|
* @param origFile Original case file
|
|
* @param headers List of table headers
|
|
* @param line CSV string representing a row of cookie table
|
|
* @return BlackboardArtifact representing one cookie table entry
|
|
* @throws TskCoreException
|
|
*/
|
|
private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
|
|
String[] lineSplit = line.split(","); // NON-NLS
|
|
|
|
String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
|
|
Long ftime = null;
|
|
try {
|
|
Long epochtime = DATE_FORMATTER.parse(accessTime).getTime();
|
|
ftime = epochtime / 1000;
|
|
} catch (ParseException ex) {
|
|
LOG.log(Level.WARNING, "The Accessed Time format in history file seems invalid " + accessTime, ex); //NON-NLS
|
|
}
|
|
|
|
String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
|
|
String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
|
|
String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
|
|
String url = flipDomain(domain);
|
|
|
|
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
|
|
bbart.addAttributes(createCookieAttributes(url, ftime, name, value, this.getName(), NetworkUtils.extractDomain(url)));
|
|
return bbart;
|
|
}
|
|
|
|
/**
|
|
* Create a BlackboardArtifact for the given row from the Edge cookie table.
|
|
*
|
|
* This function is on hold for the moment. All of the information need
|
|
* seems to be in decodedheader, but its not currently obvious how to pull
|
|
* it apart.
|
|
*
|
|
* @param origFile Original case file
|
|
* @param headers List of table headers
|
|
* @param line CSV string representing a row of download table
|
|
* @return BlackboardArtifact representing one download table entry
|
|
* @throws TskCoreException
|
|
*/
|
|
private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
|
|
BlackboardArtifact bbart = null;
|
|
|
|
String[] lineSplit = line.split(","); // NON-NLS
|
|
String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
|
|
|
|
return bbart;
|
|
}
|
|
|
|
/**
|
|
* Parse the comma separated row of information from the "Favorites" table
|
|
* of the spartan database.
|
|
*
|
|
* Note: The "Favorites" table does not have a "Creation Time"
|
|
*
|
|
* @param origFile File the table came from ie spartan.edb
|
|
* @param headers List of table column headers
|
|
* @param line The line or row of the table to parse
|
|
* @return BlackboardArtifact representation of the passed in line\table row or null if no Bookmark is found
|
|
* @throws TskCoreException
|
|
*/
|
|
private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line) throws TskCoreException {
|
|
// split on all commas as long as they are not inbetween quotes
|
|
String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
|
|
|
|
String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
|
|
String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace("\"", ""); // NON-NLS
|
|
|
|
if (url.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
|
|
bbart.addAttributes(createBookmarkAttributes(url, title, null,
|
|
this.getName(), NetworkUtils.extractDomain(url)));
|
|
return bbart;
|
|
}
|
|
|
|
/**
|
|
* Converts a space separated string of hex values to ascii characters.
|
|
*
|
|
* @param hexString
|
|
* @return "decoded" string or null if a non-hex value was found
|
|
*/
|
|
private String hexToChar(String hexString) {
|
|
String[] hexValues = hexString.split(" "); // NON-NLS
|
|
StringBuilder output = new StringBuilder();
|
|
|
|
for (String str : hexValues) {
|
|
try {
|
|
int value = Integer.parseInt(str, 16);
|
|
if (value > 31) { // Ignore non-print characters
|
|
output.append((char) value);
|
|
}
|
|
} catch (NumberFormatException ex) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return output.toString();
|
|
}
|
|
|
|
/**
|
|
* The RDomain in the WebCacheV01.data cookies tables are backwards, this
|
|
* function corrects them.
|
|
*
|
|
* Values in the RDomain appear as either com.microsoft.www or com.microsoft
|
|
* but for some reason there could also be "junk". the length checks are
|
|
* there to weed out the "junk".
|
|
*
|
|
* @param domain
|
|
* @return Correct domain string
|
|
*/
|
|
private String flipDomain(String domain) {
|
|
if (domain == null || domain.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
String[] tokens = domain.split("\\."); // NON-NLS
|
|
|
|
if (tokens.length < 2 || tokens.length > 3) {
|
|
return domain; // don't know what to do, just send it back as is
|
|
}
|
|
|
|
StringBuilder buf = new StringBuilder();
|
|
if (tokens.length > 2) {
|
|
buf.append(tokens[2]);
|
|
buf.append(".");
|
|
}
|
|
buf.append(tokens[1]);
|
|
buf.append(".");
|
|
buf.append(tokens[0]);
|
|
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Returns a list the container files that have download information in
|
|
* them.
|
|
*
|
|
* @param resultDir Path to ESEDatabaseViewer output
|
|
* @return List of download table files
|
|
*/
|
|
private ArrayList<File> getDownloadFiles(File resultDir) throws FileNotFoundException {
|
|
return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
|
|
}
|
|
|
|
/**
|
|
* Returns a list the container files that have history information in them.
|
|
*
|
|
* @param resultDir Path to ESEDatabaseViewer output
|
|
* @return List of history table files
|
|
* @throws FileNotFoundException
|
|
*/
|
|
private ArrayList<File> getHistoryFiles(File resultDir) throws FileNotFoundException {
|
|
return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of the containers files that are of the given type string
|
|
*
|
|
* @param resultDir Path to ESEDatabaseViewer output
|
|
* @param type Type of table files
|
|
* @return List of table files returns null if no files of that type are found
|
|
* @throws FileNotFoundException
|
|
*/
|
|
private ArrayList<File> getContainerFiles(File resultDir, String type) throws FileNotFoundException {
|
|
HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
|
|
|
|
ArrayList<String> idList = idTable.get(type);
|
|
if (idList == null) {
|
|
return null;
|
|
}
|
|
|
|
ArrayList<File> fileList = new ArrayList<>();
|
|
for (String str : idList) {
|
|
String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
|
|
fileList.add(new File(resultDir, fileName));
|
|
}
|
|
|
|
return fileList;
|
|
}
|
|
|
|
/**
|
|
* Opens and reads the Containers table to create a table of information
|
|
* about which of the Container_xx files contain which type of information.
|
|
*
|
|
* Each row of the "Containers" table describes one of the Container_xx
|
|
* files.
|
|
*
|
|
* @param resultDir Path to ESEDatabaseViewer output
|
|
* @return Hashmap with Key representing the table type, the value is a list of table ids for that type
|
|
*/
|
|
private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir) throws FileNotFoundException {
|
|
|
|
if (containersTable == null) {
|
|
File containerFile = new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
|
|
|
|
try (Scanner fileScanner = new Scanner(new FileInputStream(containerFile))) {
|
|
List<String> headers = null;
|
|
containersTable = new HashMap<>();
|
|
int nameIdx = 0;
|
|
int idIdx = 0;
|
|
while (fileScanner.hasNext()) {
|
|
String line = fileScanner.nextLine();
|
|
if (headers == null) {
|
|
headers = Arrays.asList(line.toLowerCase().split(","));
|
|
nameIdx = headers.indexOf(EDGE_HEAD_NAME);
|
|
idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
|
|
} else {
|
|
String[] row = line.split(","); // NON-NLS
|
|
String name = row[nameIdx];
|
|
String id = row[idIdx];
|
|
|
|
ArrayList<String> idList = containersTable.get(name);
|
|
if (idList == null) {
|
|
idList = new ArrayList<>();
|
|
containersTable.put(name, idList);
|
|
}
|
|
|
|
idList.add(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return containersTable;
|
|
}
|
|
|
|
/**
|
|
* Clears the containerTable
|
|
*/
|
|
private void clearContainerTable(){
|
|
containersTable = null;
|
|
}
|
|
}
|