19 package org.sleuthkit.autopsy.recentactivity;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Scanner;
35 import java.util.logging.Level;
36 import org.openide.modules.InstalledFileLocator;
37 import org.openide.util.NbBundle.Messages;
59 final class ExtractEdge
extends Extract {
61 private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
62 private final IngestServices services = IngestServices.getInstance();
63 private final Path moduleTempResultPath;
64 private Content dataSource;
65 private IngestJobContext context;
66 private HashMap<String, ArrayList<String>> containersTable;
68 private static final String EDGE =
"Edge";
70 private static final String EDGE_KEYWORD_VISIT =
"Visited:";
71 private static final String IGNORE_COMMA_IN_QUOTES_REGEX =
",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
73 private static final String EDGE_TABLE_TYPE_DOWNLOAD =
"iedownload";
74 private static final String EDGE_TABLE_TYPE_HISTORY =
"History";
75 private static final String EDGE_TABLE_TYPE_COOKIE =
"cookie";
77 private static final String EDGE_HEAD_URL =
"url";
78 private static final String EDGE_HEAD_ACCESSTIME =
"accessedtime";
79 private static final String EDGE_HEAD_NAME =
"name";
80 private static final String EDGE_HEAD_CONTAINER_ID =
"containerid";
81 private static final String EDGE_HEAD_RESPONSEHEAD =
"responseheaders";
82 private static final String EDGE_HEAD_TITLE =
"title";
83 private static final String EDGE_HEAD_RDOMAIN =
"rdomain";
84 private static final String EDGE_HEAD_VALUE =
"value";
85 private static final String EDGE_HEAD_LASTMOD =
"lastmodified";
87 private static final String EDGE_WEBCACHE_PREFIX =
"WebCacheV01";
88 private static final String EDGE_CONTAINER_FILE_PREFIX =
"Container_";
89 private static final String EDGE_CONTAINER_FILE_EXT =
".csv";
90 private static final String EDGE_WEBCACHE_EXT =
".dat";
92 private static final String ESE_TOOL_NAME =
"ESEDatabaseView.exe";
93 private static final String EDGE_WEBCACHE_NAME =
"WebCacheV01.dat";
94 private static final String EDGE_SPARTAN_NAME =
"Spartan.edb";
95 private static final String EDGE_CONTAINTERS_FILE_NAME =
"Containers.csv";
96 private static final String EDGE_FAVORITE_FILE_NAME =
"Favorites.csv";
97 private static final String EDGE_OUTPUT_FILE_NAME =
"Output.txt";
98 private static final String EDGE_ERROR_FILE_NAME =
"File.txt";
99 private static final String EDGE_WEBCACHE_FOLDER_NAME =
"WebCache";
100 private static final String EDGE_SPARTAN_FOLDER_NAME =
"MicrosoftEdge";
102 private static final String ESE_TOOL_FOLDER =
"ESEDatabaseView";
103 private static final String EDGE_RESULT_FOLDER_NAME =
"results";
105 private static final SimpleDateFormat DATE_FORMATTER =
new SimpleDateFormat(
"MM/dd/yyyy hh:mm:ss a");
108 "ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer",
109 "ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file",
110 "ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file",
111 "ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file",
112 "ExtractEdge_Module_Name=Microsoft Edge",
113 "ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
114 "Progress_Message_Edge_History=Microsoft Edge History",
115 "Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks",
116 "Progress_Message_Edge_Cookies=Microsoft Edge Cookies",
122 ExtractEdge() throws NoCurrentCaseException {
123 moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME);
127 protected String getName() {
128 return Bundle.ExtractEdge_Module_Name();
132 void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
133 this.dataSource = dataSource;
134 this.context = context;
135 this.setFoundData(
false);
137 List<AbstractFile> webCacheFiles = null;
138 List<AbstractFile> spartanFiles = null;
141 webCacheFiles = fetchWebCacheDBFiles();
142 }
catch (TskCoreException ex) {
143 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
144 LOG.log(Level.SEVERE,
"Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex);
148 spartanFiles = fetchSpartanDBFiles();
149 }
catch (TskCoreException ex) {
150 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
151 LOG.log(Level.SEVERE,
"Error fetching 'spartan.edb' files for Microsoft Edge", ex);
155 if (webCacheFiles == null && spartanFiles == null) {
159 this.setFoundData(
true);
161 if (!PlatformUtil.isWindowsOS()) {
162 LOG.log(Level.WARNING,
"Microsoft Edge files found, unable to parse on Non-Windows system");
166 final String esedumper = getPathForESEDumper();
167 if (esedumper == null) {
168 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
169 LOG.log(Level.SEVERE,
"Error finding ESEDatabaseViewer program");
174 this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar);
175 }
catch (IOException | TskCoreException ex) {
176 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
177 LOG.log(Level.SEVERE,
"Error returned from processWebCacheDbFile", ex);
180 progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
182 this.processSpartanDbFile(esedumper, spartanFiles);
183 }
catch (IOException | TskCoreException ex) {
184 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
185 LOG.log(Level.SEVERE,
"Error returned from processSpartanDbFile", ex);
198 void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar)
throws IOException, TskCoreException {
200 for (AbstractFile webCacheFile : webCacheFiles) {
202 if (context.dataSourceIngestIsCancelled()) {
206 clearContainerTable();
209 String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
210 + Integer.toString((
int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT;
211 File tempWebCacheFile =
new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName);
214 ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
215 context::dataSourceIngestIsCancelled);
216 }
catch (IOException ex) {
217 throw new IOException(
"Error writingToFile: " + webCacheFile, ex);
220 File resultsDir =
new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((
int) webCacheFile.getId()));
223 executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
224 resultsDir.getAbsolutePath());
226 if (context.dataSourceIngestIsCancelled()) {
230 progressBar.progress(Bundle.Progress_Message_Edge_History());
232 this.getHistory(webCacheFile, resultsDir);
234 if (context.dataSourceIngestIsCancelled()) {
238 progressBar.progress(Bundle.Progress_Message_Edge_Cookies());
240 this.getCookies(webCacheFile, resultsDir);
243 tempWebCacheFile.delete();
244 FileUtil.deleteFileDir(resultsDir);
258 void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles)
throws IOException, TskCoreException {
260 for (AbstractFile spartanFile : spartanFiles) {
262 if (context.dataSourceIngestIsCancelled()) {
267 String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
268 + Integer.toString((
int) spartanFile.getId()) + EDGE_WEBCACHE_EXT;
269 File tempSpartanFile =
new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName);
272 ContentUtils.writeToFile(spartanFile, tempSpartanFile,
273 context::dataSourceIngestIsCancelled);
274 }
catch (IOException ex) {
275 throw new IOException(
"Error writingToFile: " + spartanFile, ex);
278 File resultsDir =
new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((
int) spartanFile.getId()));
281 executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
282 resultsDir.getAbsolutePath());
284 if (context.dataSourceIngestIsCancelled()) {
288 this.getBookmarks(spartanFile, resultsDir);
291 tempSpartanFile.delete();
292 FileUtil.deleteFileDir(resultsDir);
306 private void getHistory(AbstractFile origFile, File resultDir)
throws TskCoreException, FileNotFoundException {
307 ArrayList<File> historyFiles = getHistoryFiles(resultDir);
309 if (historyFiles == null) {
313 for (File file : historyFiles) {
314 if (context.dataSourceIngestIsCancelled()) {
320 fileScanner =
new Scanner(
new FileInputStream(file.toString()));
321 }
catch (FileNotFoundException ex) {
322 LOG.log(Level.WARNING,
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex);
326 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
329 List<String> headers = null;
330 while (fileScanner.hasNext()) {
331 if (context.dataSourceIngestIsCancelled()) {
335 String line = fileScanner.nextLine();
336 if (headers == null) {
337 headers = Arrays.asList(line.toLowerCase().split(
","));
341 if (line.contains(EDGE_KEYWORD_VISIT)) {
342 BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
345 this.indexArtifact(ba);
353 if (!bbartifacts.isEmpty()) {
354 services.fireModuleDataEvent(
new ModuleDataEvent(
355 RecentActivityExtracterModuleFactory.getModuleName(),
356 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
369 private void getBookmarks(AbstractFile origFile, File resultDir)
throws TskCoreException {
371 File favoriteFile =
new File(resultDir, EDGE_FAVORITE_FILE_NAME);
374 fileScanner =
new Scanner(
new FileInputStream(favoriteFile));
375 }
catch (FileNotFoundException ex) {
381 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
384 List<String> headers = null;
385 while (fileScanner.hasNext()) {
386 String line = fileScanner.nextLine();
387 if (headers == null) {
388 headers = Arrays.asList(line.toLowerCase().split(
","));
392 BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
395 this.indexArtifact(ba);
402 if (!bbartifacts.isEmpty()) {
403 services.fireModuleDataEvent(
new ModuleDataEvent(
404 RecentActivityExtracterModuleFactory.getModuleName(),
405 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
416 private void getCookies(AbstractFile origFile, File resultDir)
throws TskCoreException {
417 File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
419 if (containerFiles == null) {
423 for (File file : containerFiles) {
424 if (context.dataSourceIngestIsCancelled()) {
430 fileScanner =
new Scanner(
new FileInputStream(file.toString()));
431 }
catch (FileNotFoundException ex) {
432 LOG.log(Level.WARNING,
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex);
436 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
439 List<String> headers = null;
440 while (fileScanner.hasNext()) {
441 if (context.dataSourceIngestIsCancelled()) {
445 String line = fileScanner.nextLine();
446 if (headers == null) {
447 headers = Arrays.asList(line.toLowerCase().split(
","));
451 BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
454 this.indexArtifact(ba);
461 if (!bbartifacts.isEmpty()) {
462 services.fireModuleDataEvent(
new ModuleDataEvent(
463 RecentActivityExtracterModuleFactory.getModuleName(),
464 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
479 private void getDownloads(AbstractFile origFile, File resultDir)
throws TskCoreException, FileNotFoundException {
480 ArrayList<File> downloadFiles = getDownloadFiles(resultDir);
482 if (downloadFiles == null) {
486 for (File file : downloadFiles) {
487 if (context.dataSourceIngestIsCancelled()) {
493 fileScanner =
new Scanner(
new FileInputStream(file.toString()));
494 }
catch (FileNotFoundException ex) {
495 LOG.log(Level.WARNING,
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex);
498 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
501 List<String> headers = null;
502 while (fileScanner.hasNext()) {
503 if (context.dataSourceIngestIsCancelled()) {
507 String line = fileScanner.nextLine();
508 if (headers == null) {
509 headers = Arrays.asList(line.toLowerCase().split(
","));
513 if (line.contains(EDGE_TABLE_TYPE_DOWNLOAD)) {
515 BlackboardArtifact ba = getDownloadArtifact(origFile, headers, line);
518 this.indexArtifact(ba);
526 if (!bbartifacts.isEmpty()) {
527 services.fireModuleDataEvent(
new ModuleDataEvent(
528 RecentActivityExtracterModuleFactory.getModuleName(),
529 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
539 private String getPathForESEDumper() {
540 Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME);
541 File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
542 ExtractEdge.class.getPackage().getName(),
false);
543 if (eseToolFile != null) {
544 return eseToolFile.getAbsolutePath();
556 private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
558 = currentCase.getServices().getFileManager();
559 return fileManager.
findFiles(dataSource, EDGE_WEBCACHE_NAME, EDGE_WEBCACHE_FOLDER_NAME);
568 private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
570 = currentCase.getServices().getFileManager();
571 return fileManager.
findFiles(dataSource, EDGE_SPARTAN_NAME, EDGE_SPARTAN_FOLDER_NAME);
585 private void executeDumper(String dumperPath, String inputFilePath,
586 String outputDir)
throws IOException {
588 final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
589 final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
590 LOG.log(Level.INFO,
"Writing ESEDatabaseViewer results to: {0}", outputDir);
592 List<String> commandLine =
new ArrayList<>();
593 commandLine.add(dumperPath);
594 commandLine.add(
"/table");
595 commandLine.add(inputFilePath);
596 commandLine.add(
"*");
597 commandLine.add(
"/scomma");
598 commandLine.add(outputDir +
"\\" +
"*.csv");
600 ProcessBuilder processBuilder =
new ProcessBuilder(commandLine);
601 processBuilder.redirectOutput(outputFilePath.toFile());
602 processBuilder.redirectError(errFilePath.toFile());
604 ExecUtil.execute(processBuilder,
new DataSourceIngestModuleProcessTerminator(context));
617 private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
618 String[] rowSplit = line.split(
",");
620 int index = headers.indexOf(EDGE_HEAD_URL);
621 String urlUserStr = rowSplit[index];
623 String[] str = urlUserStr.split(
"@");
624 String user = (str[0].replace(EDGE_KEYWORD_VISIT,
"")).trim();
627 index = headers.indexOf(EDGE_HEAD_ACCESSTIME);
628 String accessTime = rowSplit[index].trim();
631 Long epochtime = DATE_FORMATTER.parse(accessTime).getTime();
632 ftime = epochtime / 1000;
633 }
catch (ParseException ex) {
634 LOG.log(Level.WARNING,
"The Accessed Time format in history file seems invalid " + accessTime, ex);
637 BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
639 bbart.addAttributes(createHistoryAttribute(url, ftime,
642 NetworkUtils.extractDomain(url), user));
656 private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
657 String[] lineSplit = line.split(
",");
659 String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
662 Long epochtime = DATE_FORMATTER.parse(accessTime).getTime();
663 ftime = epochtime / 1000;
664 }
catch (ParseException ex) {
665 LOG.log(Level.WARNING,
"The Accessed Time format in history file seems invalid " + accessTime, ex);
668 String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
669 String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
670 String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
671 String url = flipDomain(domain);
673 BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
674 bbart.addAttributes(createCookieAttributes(url, ftime, name, value, this.getName(), NetworkUtils.extractDomain(url)));
691 private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
692 BlackboardArtifact bbart = null;
694 String[] lineSplit = line.split(
",");
695 String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
712 private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
714 String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
716 String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
717 String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace(
"\"",
"");
723 BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
724 bbart.addAttributes(createBookmarkAttributes(url, title, null,
725 this.getName(), NetworkUtils.extractDomain(url)));
735 private String hexToChar(String hexString) {
736 String[] hexValues = hexString.split(
" ");
737 StringBuilder output =
new StringBuilder();
739 for (String str : hexValues) {
741 int value = Integer.parseInt(str, 16);
743 output.append((
char) value);
745 }
catch (NumberFormatException ex) {
750 return output.toString();
764 private String flipDomain(String domain) {
765 if (domain == null || domain.isEmpty()) {
769 String[] tokens = domain.split(
"\\.");
771 if (tokens.length < 2 || tokens.length > 3) {
775 StringBuilder buf =
new StringBuilder();
776 if (tokens.length > 2) {
777 buf.append(tokens[2]);
780 buf.append(tokens[1]);
782 buf.append(tokens[0]);
784 return buf.toString();
794 private ArrayList<File> getDownloadFiles(File resultDir)
throws FileNotFoundException {
795 return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
805 private ArrayList<File> getHistoryFiles(File resultDir)
throws FileNotFoundException {
806 return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
817 private ArrayList<File> getContainerFiles(File resultDir, String type)
throws FileNotFoundException {
818 HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
820 ArrayList<String> idList = idTable.get(type);
821 if (idList == null) {
825 ArrayList<File> fileList =
new ArrayList<>();
826 for (String str : idList) {
827 String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
828 fileList.add(
new File(resultDir, fileName));
844 private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir)
throws FileNotFoundException {
846 if (containersTable == null) {
847 File containerFile =
new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
849 try (Scanner fileScanner =
new Scanner(
new FileInputStream(containerFile))) {
850 List<String> headers = null;
851 containersTable =
new HashMap<>();
854 while (fileScanner.hasNext()) {
855 String line = fileScanner.nextLine();
856 if (headers == null) {
857 headers = Arrays.asList(line.toLowerCase().split(
","));
858 nameIdx = headers.indexOf(EDGE_HEAD_NAME);
859 idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
861 String[] row = line.split(
",");
862 String name = row[nameIdx];
863 String
id = row[idIdx];
865 ArrayList<String> idList = containersTable.get(name);
866 if (idList == null) {
867 idList =
new ArrayList<>();
868 containersTable.put(name, idList);
877 return containersTable;
883 private void clearContainerTable(){
884 containersTable = null;
synchronized List< AbstractFile > findFiles(String fileName)