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.util.Date;
28 import java.text.ParseException;
29 import java.text.SimpleDateFormat;
30 import java.text.DateFormat;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Scanner;
38 import java.util.logging.Level;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 import org.openide.modules.InstalledFileLocator;
42 import org.openide.util.NbBundle.Messages;
62 final class ExtractEdge
extends Extract {
64 private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
65 private final Path moduleTempResultPath;
66 private Content dataSource;
67 private IngestJobContext context;
68 private HashMap<String, ArrayList<String>> containersTable;
70 private static final String EDGE =
"Edge";
72 private static final String EDGE_KEYWORD_VISIT =
"Visited:";
73 private static final String IGNORE_COMMA_IN_QUOTES_REGEX =
",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
75 private static final String EDGE_TABLE_TYPE_DOWNLOAD =
"iedownload";
76 private static final String EDGE_TABLE_TYPE_HISTORY =
"History";
77 private static final String EDGE_TABLE_TYPE_COOKIE =
"cookie";
79 private static final String EDGE_HEAD_URL =
"url";
80 private static final String EDGE_HEAD_ACCESSTIME =
"accessedtime";
81 private static final String EDGE_HEAD_NAME =
"name";
82 private static final String EDGE_HEAD_CONTAINER_ID =
"containerid";
83 private static final String EDGE_HEAD_RESPONSEHEAD =
"responseheaders";
84 private static final String EDGE_HEAD_TITLE =
"title";
85 private static final String EDGE_HEAD_RDOMAIN =
"rdomain";
86 private static final String EDGE_HEAD_VALUE =
"value";
87 private static final String EDGE_HEAD_LASTMOD =
"lastmodified";
89 private static final String EDGE_WEBCACHE_PREFIX =
"WebCacheV01";
90 private static final String EDGE_CONTAINER_FILE_PREFIX =
"Container_";
91 private static final String EDGE_CONTAINER_FILE_EXT =
".csv";
92 private static final String EDGE_WEBCACHE_EXT =
".dat";
94 private static final String ESE_TOOL_NAME =
"ESEDatabaseView.exe";
95 private static final String EDGE_WEBCACHE_NAME =
"WebCacheV01.dat";
96 private static final String EDGE_SPARTAN_NAME =
"Spartan.edb";
97 private static final String EDGE_CONTAINTERS_FILE_NAME =
"Containers.csv";
98 private static final String EDGE_FAVORITE_FILE_NAME =
"Favorites.csv";
99 private static final String EDGE_OUTPUT_FILE_NAME =
"Output.txt";
100 private static final String EDGE_ERROR_FILE_NAME =
"File.txt";
101 private static final String EDGE_WEBCACHE_FOLDER_NAME =
"WebCache";
102 private static final String EDGE_SPARTAN_FOLDER_NAME =
"MicrosoftEdge";
104 private static final String ESE_TOOL_FOLDER =
"ESEDatabaseView";
105 private static final String EDGE_RESULT_FOLDER_NAME =
"results";
109 private SimpleDateFormat previouslyValidDateFormat = null;
112 "ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer",
113 "ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file",
114 "ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file",
115 "ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file",
116 "ExtractEdge_Module_Name=Microsoft Edge",
117 "ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
118 "Progress_Message_Edge_History=Microsoft Edge History",
119 "Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks",
120 "Progress_Message_Edge_Cookies=Microsoft Edge Cookies",
126 ExtractEdge() throws NoCurrentCaseException {
127 moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME);
131 protected String getName() {
132 return Bundle.ExtractEdge_Module_Name();
136 void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
137 this.dataSource = dataSource;
138 this.context = context;
139 this.setFoundData(
false);
141 List<AbstractFile> webCacheFiles = null;
142 List<AbstractFile> spartanFiles = null;
145 webCacheFiles = fetchWebCacheDBFiles();
146 }
catch (TskCoreException ex) {
147 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
148 LOG.log(Level.SEVERE,
"Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex);
152 spartanFiles = fetchSpartanDBFiles();
153 }
catch (TskCoreException ex) {
154 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
155 LOG.log(Level.SEVERE,
"Error fetching 'spartan.edb' files for Microsoft Edge", ex);
159 if (webCacheFiles == null && spartanFiles == null) {
163 this.setFoundData(
true);
165 if (!PlatformUtil.isWindowsOS()) {
166 LOG.log(Level.WARNING,
"Microsoft Edge files found, unable to parse on Non-Windows system");
170 final String esedumper = getPathForESEDumper();
171 if (esedumper == null) {
172 LOG.log(Level.SEVERE,
"Error finding ESEDatabaseViewer program");
173 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
178 this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar);
179 }
catch (IOException | TskCoreException ex) {
180 LOG.log(Level.SEVERE,
"Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex);
181 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
184 progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
186 this.processSpartanDbFile(esedumper, spartanFiles);
187 }
catch (IOException | TskCoreException ex) {
188 LOG.log(Level.SEVERE,
"Error processing 'spartan.edb' files for Microsoft Edge", ex);
189 this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
202 void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar)
throws IOException, TskCoreException {
204 for (AbstractFile webCacheFile : webCacheFiles) {
206 if (context.dataSourceIngestIsCancelled()) {
210 clearContainerTable();
213 String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
214 + Integer.toString((
int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT;
215 File tempWebCacheFile =
new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName);
218 ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
219 context::dataSourceIngestIsCancelled);
220 }
catch (IOException ex) {
221 throw new IOException(
"Error writingToFile: " + webCacheFile, ex);
224 File resultsDir =
new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((
int) webCacheFile.getId()));
227 executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
228 resultsDir.getAbsolutePath());
230 if (context.dataSourceIngestIsCancelled()) {
234 progressBar.progress(Bundle.Progress_Message_Edge_History());
236 this.getHistory(webCacheFile, resultsDir);
238 if (context.dataSourceIngestIsCancelled()) {
242 progressBar.progress(Bundle.Progress_Message_Edge_Cookies());
244 this.getCookies(webCacheFile, resultsDir);
247 tempWebCacheFile.delete();
248 FileUtil.deleteFileDir(resultsDir);
262 void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles)
throws IOException, TskCoreException {
264 for (AbstractFile spartanFile : spartanFiles) {
266 if (context.dataSourceIngestIsCancelled()) {
271 String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
272 + Integer.toString((
int) spartanFile.getId()) + EDGE_WEBCACHE_EXT;
273 File tempSpartanFile =
new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName);
276 ContentUtils.writeToFile(spartanFile, tempSpartanFile,
277 context::dataSourceIngestIsCancelled);
278 }
catch (IOException ex) {
279 throw new IOException(
"Error writingToFile: " + spartanFile, ex);
282 File resultsDir =
new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((
int) spartanFile.getId()));
285 executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
286 resultsDir.getAbsolutePath());
288 if (context.dataSourceIngestIsCancelled()) {
292 this.getBookmarks(spartanFile, resultsDir);
295 tempSpartanFile.delete();
296 FileUtil.deleteFileDir(resultsDir);
310 private void getHistory(AbstractFile origFile, File resultDir)
throws TskCoreException, FileNotFoundException {
311 ArrayList<File> historyFiles = getHistoryFiles(resultDir);
313 if (historyFiles == null) {
317 for (File file : historyFiles) {
318 if (context.dataSourceIngestIsCancelled()) {
324 fileScanner =
new Scanner(
new FileInputStream(file.toString()));
325 }
catch (FileNotFoundException ex) {
326 LOG.log(Level.WARNING,
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex);
330 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
333 List<String> headers = null;
334 while (fileScanner.hasNext()) {
335 if (context.dataSourceIngestIsCancelled()) {
339 String line = fileScanner.nextLine();
340 if (headers == null) {
341 headers = Arrays.asList(line.toLowerCase().split(
","));
345 if (line.contains(EDGE_KEYWORD_VISIT)) {
346 BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
356 if (!bbartifacts.isEmpty()) {
357 postArtifacts(bbartifacts);
370 private void getBookmarks(AbstractFile origFile, File resultDir)
throws TskCoreException {
372 File favoriteFile =
new File(resultDir, EDGE_FAVORITE_FILE_NAME);
375 fileScanner =
new Scanner(
new FileInputStream(favoriteFile));
376 }
catch (FileNotFoundException ex) {
382 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
385 List<String> headers = null;
386 while (fileScanner.hasNext()) {
387 String line = fileScanner.nextLine();
388 if (headers == null) {
389 headers = Arrays.asList(line.toLowerCase().split(
","));
393 BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
402 if (!bbartifacts.isEmpty()) {
403 postArtifacts(bbartifacts);
414 private void getCookies(AbstractFile origFile, File resultDir)
throws TskCoreException {
415 File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
417 if (containerFiles == null) {
421 for (File file : containerFiles) {
422 if (context.dataSourceIngestIsCancelled()) {
428 fileScanner =
new Scanner(
new FileInputStream(file.toString()));
429 }
catch (FileNotFoundException ex) {
430 LOG.log(Level.WARNING,
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex);
434 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
437 List<String> headers = null;
438 while (fileScanner.hasNext()) {
439 if (context.dataSourceIngestIsCancelled()) {
443 String line = fileScanner.nextLine();
444 if (headers == null) {
445 headers = Arrays.asList(line.toLowerCase().split(
","));
449 BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
458 if (!bbartifacts.isEmpty()) {
459 postArtifacts(bbartifacts);
474 private void getDownloads(AbstractFile origFile, File resultDir)
throws TskCoreException, FileNotFoundException {
475 ArrayList<File> downloadFiles = getDownloadFiles(resultDir);
477 if (downloadFiles == null) {
481 for (File file : downloadFiles) {
482 if (context.dataSourceIngestIsCancelled()) {
488 fileScanner =
new Scanner(
new FileInputStream(file.toString()));
489 }
catch (FileNotFoundException ex) {
490 LOG.log(Level.WARNING,
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex);
493 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
496 List<String> headers = null;
497 while (fileScanner.hasNext()) {
498 if (context.dataSourceIngestIsCancelled()) {
502 String line = fileScanner.nextLine();
503 if (headers == null) {
504 headers = Arrays.asList(line.toLowerCase().split(
","));
508 if (line.contains(EDGE_TABLE_TYPE_DOWNLOAD)) {
510 BlackboardArtifact ba = getDownloadArtifact(origFile, headers, line);
520 postArtifacts(bbartifacts);
529 private String getPathForESEDumper() {
530 Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME);
531 File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
532 ExtractEdge.class.getPackage().getName(),
false);
533 if (eseToolFile != null) {
534 return eseToolFile.getAbsolutePath();
546 private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
548 = currentCase.getServices().getFileManager();
549 return fileManager.
findFiles(dataSource, EDGE_WEBCACHE_NAME, EDGE_WEBCACHE_FOLDER_NAME);
558 private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
560 = currentCase.getServices().getFileManager();
561 return fileManager.
findFiles(dataSource, EDGE_SPARTAN_NAME, EDGE_SPARTAN_FOLDER_NAME);
575 private void executeDumper(String dumperPath, String inputFilePath,
576 String outputDir)
throws IOException {
578 final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
579 final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
580 LOG.log(Level.INFO,
"Writing ESEDatabaseViewer results to: {0}", outputDir);
582 List<String> commandLine =
new ArrayList<>();
583 commandLine.add(dumperPath);
584 commandLine.add(
"/table");
585 commandLine.add(inputFilePath);
586 commandLine.add(
"*");
587 commandLine.add(
"/scomma");
588 commandLine.add(outputDir +
"\\" +
"*.csv");
590 ProcessBuilder processBuilder =
new ProcessBuilder(commandLine);
591 processBuilder.redirectOutput(outputFilePath.toFile());
592 processBuilder.redirectError(errFilePath.toFile());
594 ExecUtil.execute(processBuilder,
new DataSourceIngestModuleProcessTerminator(context,
true));
607 private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
608 String[] rowSplit = line.split(
",");
610 int index = headers.indexOf(EDGE_HEAD_URL);
611 String urlUserStr = rowSplit[index];
613 String[] str = urlUserStr.split(
"@");
614 String user = (str[0].replace(EDGE_KEYWORD_VISIT,
"")).trim();
617 index = headers.indexOf(EDGE_HEAD_ACCESSTIME);
618 String accessTime = rowSplit[index].trim();
619 Long ftime = parseTimestamp(accessTime);
621 BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
623 bbart.addAttributes(createHistoryAttribute(url, ftime,
626 NetworkUtils.extractDomain(url), user));
640 private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
641 String[] lineSplit = line.split(
",");
643 String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
644 Long ftime = parseTimestamp(accessTime);
646 String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
647 String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
648 String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
649 String url = flipDomain(domain);
651 BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
652 bbart.addAttributes(createCookieAttributes(url, ftime, name, value, this.getName(), NetworkUtils.extractDomain(url)));
669 private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
670 BlackboardArtifact bbart = null;
672 String[] lineSplit = line.split(
",");
673 String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
690 private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line)
throws TskCoreException {
692 String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
694 String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
695 String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace(
"\"",
"");
701 BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
702 bbart.addAttributes(createBookmarkAttributes(url, title, null,
703 this.getName(), NetworkUtils.extractDomain(url)));
722 private Long parseTimestamp(String timeStr) {
725 if (previouslyValidDateFormat != null) {
727 return previouslyValidDateFormat.parse(timeStr).getTime() / 1000;
728 }
catch (ParseException ex) {
735 SimpleDateFormat usDateFormat =
new SimpleDateFormat(
"MM/dd/yyyy hh:mm:ss a");
736 usDateFormat.setLenient(
false);
737 Long epochTime = usDateFormat.parse(timeStr).getTime();
738 previouslyValidDateFormat = usDateFormat;
739 return epochTime / 1000;
740 }
catch (ParseException ex) {
746 boolean monthFirstFromLocale =
true;
747 String localeDatePattern = ((SimpleDateFormat) DateFormat.getDateInstance(
748 DateFormat.SHORT, Locale.getDefault())).toPattern();
749 if (localeDatePattern.startsWith(
"d")) {
750 monthFirstFromLocale =
false;
755 boolean monthFirst = monthFirstFromLocale;
756 Pattern pattern = Pattern.compile(
"^([0-9]{1,2})[^0-9]([0-9]{1,2})");
757 Matcher matcher = pattern.matcher(timeStr);
758 if (matcher.find()) {
759 int firstVal = Integer.parseInt(matcher.group(1));
760 int secondVal = Integer.parseInt(matcher.group(2));
764 }
else if (secondVal > 12) {
771 boolean hasAmPm =
false;
772 if (timeStr.endsWith(
"M") || timeStr.endsWith(
"m")) {
777 boolean hasSlashes =
false;
778 if (timeStr.contains(
"/")) {
783 String dateFormatPattern;
786 dateFormatPattern =
"MM/dd/yyyy ";
788 dateFormatPattern =
"MM.dd.yyyy ";
792 dateFormatPattern =
"dd/MM/yyyy ";
794 dateFormatPattern =
"dd.MM.yyyy ";
799 dateFormatPattern +=
"hh:mm:ss a";
801 dateFormatPattern +=
"HH:mm:ss";
805 SimpleDateFormat dateFormat =
new SimpleDateFormat(dateFormatPattern);
806 dateFormat.setLenient(
false);
807 Long epochTime = dateFormat.parse(timeStr).getTime();
808 previouslyValidDateFormat = dateFormat;
809 return epochTime / 1000;
810 }
catch (ParseException ex) {
811 LOG.log(Level.WARNING,
"Timestamp could not be parsed ({0})", timeStr);
822 private String hexToChar(String hexString) {
823 String[] hexValues = hexString.split(
" ");
824 StringBuilder output =
new StringBuilder();
826 for (String str : hexValues) {
828 int value = Integer.parseInt(str, 16);
830 output.append((
char) value);
832 }
catch (NumberFormatException ex) {
837 return output.toString();
851 private String flipDomain(String domain) {
852 if (domain == null || domain.isEmpty()) {
856 String[] tokens = domain.split(
"\\.");
858 if (tokens.length < 2 || tokens.length > 3) {
862 StringBuilder buf =
new StringBuilder();
863 if (tokens.length > 2) {
864 buf.append(tokens[2]);
867 buf.append(tokens[1]);
869 buf.append(tokens[0]);
871 return buf.toString();
881 private ArrayList<File> getDownloadFiles(File resultDir)
throws FileNotFoundException {
882 return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
892 private ArrayList<File> getHistoryFiles(File resultDir)
throws FileNotFoundException {
893 return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
904 private ArrayList<File> getContainerFiles(File resultDir, String type)
throws FileNotFoundException {
905 HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
907 ArrayList<String> idList = idTable.get(type);
908 if (idList == null) {
912 ArrayList<File> fileList =
new ArrayList<>();
913 for (String str : idList) {
914 String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
915 fileList.add(
new File(resultDir, fileName));
931 private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir)
throws FileNotFoundException {
933 if (containersTable == null) {
934 File containerFile =
new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
936 try (Scanner fileScanner =
new Scanner(
new FileInputStream(containerFile))) {
937 List<String> headers = null;
938 containersTable =
new HashMap<>();
941 while (fileScanner.hasNext()) {
942 String line = fileScanner.nextLine();
943 if (headers == null) {
944 headers = Arrays.asList(line.toLowerCase().split(
","));
945 nameIdx = headers.indexOf(EDGE_HEAD_NAME);
946 idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
948 String[] row = line.split(
",");
949 String name = row[nameIdx];
950 String
id = row[idIdx];
952 ArrayList<String> idList = containersTable.get(name);
953 if (idList == null) {
954 idList =
new ArrayList<>();
955 containersTable.put(name, idList);
964 return containersTable;
970 private void clearContainerTable(){
971 containersTable = null;
synchronized List< AbstractFile > findFiles(String fileName)