Autopsy  4.11.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractSafari.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2019 Basis Technology Corp.
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.recentactivity;
20 
21 import com.dd.plist.NSArray;
22 import com.dd.plist.NSDate;
23 import com.dd.plist.NSDictionary;
24 import com.dd.plist.NSObject;
25 import com.dd.plist.NSString;
26 import com.dd.plist.PropertyListFormatException;
27 import com.dd.plist.PropertyListParser;
28 import java.io.File;
29 import java.io.IOException;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.text.ParseException;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.logging.Level;
39 import javax.xml.parsers.ParserConfigurationException;
40 import org.apache.commons.io.FilenameUtils;
41 import org.openide.util.NbBundle.Messages;
51 import org.sleuthkit.datamodel.AbstractFile;
52 import org.sleuthkit.datamodel.BlackboardArtifact;
53 import org.sleuthkit.datamodel.Content;
54 import org.sleuthkit.datamodel.TskCoreException;
55 import org.xml.sax.SAXException;
56 
61 final class ExtractSafari extends Extract {
62 
63  private final IngestServices services = IngestServices.getInstance();
64 
65  // visit_time uses an epoch of Jan 1, 2001 thus the addition of 978307200
66  private static final String HISTORY_QUERY = "SELECT url, title, visit_time + 978307200 as time FROM 'history_items' JOIN history_visits ON history_item = history_items.id;"; //NON-NLS
67 
68  private static final String HISTORY_FILE_NAME = "History.db"; //NON-NLS
69  private static final String BOOKMARK_FILE_NAME = "Bookmarks.plist"; //NON-NLS
70  private static final String DOWNLOAD_FILE_NAME = "Downloads.plist"; //NON-NLS
71  private static final String COOKIE_FILE_NAME = "Cookies.binarycookies"; //NON-NLS
72  private static final String COOKIE_FOLDER = "Cookies";
73  private static final String SAFARI_FOLDER = "Safari";
74 
75  private static final String HEAD_URL = "url"; //NON-NLS
76  private static final String HEAD_TITLE = "title"; //NON-NLS
77  private static final String HEAD_TIME = "time"; //NON-NLS
78 
79  private static final String PLIST_KEY_CHILDREN = "Children"; //NON-NLS
80  private static final String PLIST_KEY_URL = "URLString"; //NON-NLS
81  private static final String PLIST_KEY_URI = "URIDictionary"; //NON-NLS
82  private static final String PLIST_KEY_TITLE = "title"; //NON-NLS
83  private static final String PLIST_KEY_DOWNLOAD_URL = "DownloadEntryURL"; //NON-NLS
84  private static final String PLIST_KEY_DOWNLOAD_DATE = "DownloadEntryDateAddedKey"; //NON-NLS
85  private static final String PLIST_KEY_DOWNLOAD_PATH = "DownloadEntryPath"; //NON-NLS
86  private static final String PLIST_KEY_DOWNLOAD_HISTORY = "DownloadHistory"; //NON-NLS
87 
88  private static final Logger LOG = Logger.getLogger(ExtractSafari.class.getName());
89 
90  @Messages({
91  "ExtractSafari_Module_Name=Safari",
92  "ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files.",
93  "ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files",
94  "ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files",
95  "Progress_Message_Safari_History=Safari History",
96  "Progress_Message_Safari_Bookmarks=Safari Bookmarks",
97  "Progress_Message_Safari_Cookies=Safari Cookies",
98  "Progress_Message_Safari_Downloads=Safari Downloads",
99  })
100 
105  ExtractSafari() {
106 
107  }
108 
109  @Override
110  protected String getName() {
111  return Bundle.ExtractSafari_Module_Name();
112  }
113 
114  @Override
115  void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
116  setFoundData(false);
117 
118  progressBar.progress(Bundle.Progress_Message_Safari_Cookies());
119  try {
120  processHistoryDB(dataSource, context);
121 
122  } catch (IOException | TskCoreException ex) {
123  this.addErrorMessage(Bundle.ExtractSafari_Error_Getting_History());
124  LOG.log(Level.SEVERE, "Exception thrown while processing history file.", ex); //NON-NLS
125  }
126 
127  progressBar.progress(Bundle.Progress_Message_Safari_Bookmarks());
128  try {
129  processBookmarkPList(dataSource, context);
130  } catch (IOException | TskCoreException | SAXException | PropertyListFormatException | ParseException | ParserConfigurationException ex) {
131  this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Bookmark());
132  LOG.log(Level.SEVERE, "Exception thrown while parsing Safari Bookmarks file.", ex); //NON-NLS
133  }
134 
135  progressBar.progress(Bundle.Progress_Message_Safari_Downloads());
136  try {
137  processDownloadsPList(dataSource, context);
138  } catch (IOException | TskCoreException | SAXException | PropertyListFormatException | ParseException | ParserConfigurationException ex) {
139  this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Bookmark());
140  LOG.log(Level.SEVERE, "Exception thrown while parsing Safari Download.plist file.", ex); //NON-NLS
141  }
142 
143  progressBar.progress(Bundle.Progress_Message_Safari_Cookies());
144  try {
145  processBinaryCookieFile(dataSource, context);
146  } catch (TskCoreException ex) {
147  this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Cookies());
148  LOG.log(Level.SEVERE, "Exception thrown while processing Safari cookies file.", ex); //NON-NLS
149  }
150  }
151 
159  private void processHistoryDB(Content dataSource, IngestJobContext context) throws TskCoreException, IOException {
160  FileManager fileManager = getCurrentCase().getServices().getFileManager();
161 
162  List<AbstractFile> historyFiles = fileManager.findFiles(dataSource, HISTORY_FILE_NAME, SAFARI_FOLDER);
163 
164  if (historyFiles == null || historyFiles.isEmpty()) {
165  return;
166  }
167 
168  setFoundData(true);
169 
170  for (AbstractFile historyFile : historyFiles) {
171  if (context.dataSourceIngestIsCancelled()) {
172  break;
173  }
174 
175  getHistory(context, historyFile);
176  }
177  }
178 
190  private void processBookmarkPList(Content dataSource, IngestJobContext context) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
191  FileManager fileManager = getCurrentCase().getServices().getFileManager();
192 
193  List<AbstractFile> files = fileManager.findFiles(dataSource, BOOKMARK_FILE_NAME, SAFARI_FOLDER);
194 
195  if (files == null || files.isEmpty()) {
196  return;
197  }
198 
199  setFoundData(true);
200 
201  for (AbstractFile file : files) {
202  if (context.dataSourceIngestIsCancelled()) {
203  break;
204  }
205 
206  getBookmarks(context, file);
207  }
208  }
209 
222  private void processDownloadsPList(Content dataSource, IngestJobContext context) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
223  FileManager fileManager = getCurrentCase().getServices().getFileManager();
224 
225  List<AbstractFile> files = fileManager.findFiles(dataSource, DOWNLOAD_FILE_NAME, SAFARI_FOLDER);
226 
227  if (files == null || files.isEmpty()) {
228  return;
229  }
230 
231  setFoundData(true);
232 
233  for (AbstractFile file : files) {
234  if (context.dataSourceIngestIsCancelled()) {
235  break;
236  }
237 
238  getDownloads(dataSource, context, file);
239  }
240  }
241 
249  private void processBinaryCookieFile(Content dataSource, IngestJobContext context) throws TskCoreException {
250  FileManager fileManager = getCurrentCase().getServices().getFileManager();
251 
252  List<AbstractFile> files = fileManager.findFiles(dataSource, COOKIE_FILE_NAME, COOKIE_FOLDER);
253 
254  if (files == null || files.isEmpty()) {
255  return;
256  }
257 
258  setFoundData(true);
259 
260  for (AbstractFile file : files) {
261  if (context.dataSourceIngestIsCancelled()) {
262  break;
263  }
264  try {
265  getCookies(context, file);
266  } catch (IOException ex) {
267  LOG.log(Level.WARNING, String.format("Failed to get cookies from file %s", Paths.get(file.getUniquePath(), file.getName()).toString()), ex);
268  }
269  }
270  }
271 
280  private void getHistory(IngestJobContext context, AbstractFile historyFile) throws TskCoreException, IOException {
281  if (historyFile.getSize() == 0) {
282  return;
283  }
284 
285  File tempHistoryFile = createTemporaryFile(context, historyFile);
286 
287  try {
288  ContentUtils.writeToFile(historyFile, tempHistoryFile, context::dataSourceIngestIsCancelled);
289  } catch (IOException ex) {
290  throw new IOException("Error writingToFile: " + historyFile, ex); //NON-NLS
291  }
292 
293  try {
294  Collection<BlackboardArtifact> bbartifacts = getHistoryArtifacts(historyFile, tempHistoryFile.toPath(), context);
295  if (!bbartifacts.isEmpty()) {
296  services.fireModuleDataEvent(new ModuleDataEvent(
297  RecentActivityExtracterModuleFactory.getModuleName(),
298  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
299  }
300  } finally {
301  tempHistoryFile.delete();
302  }
303  }
304 
318  private void getBookmarks(IngestJobContext context, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
319  if (file.getSize() == 0) {
320  return;
321  }
322 
323  File tempFile = createTemporaryFile(context, file);
324 
325  try {
326  Collection<BlackboardArtifact> bbartifacts = getBookmarkArtifacts(file, tempFile, context);
327  if (!bbartifacts.isEmpty()) {
328  services.fireModuleDataEvent(new ModuleDataEvent(
329  RecentActivityExtracterModuleFactory.getModuleName(),
330  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bbartifacts));
331  }
332  } finally {
333  tempFile.delete();
334  }
335 
336  }
337 
351  private void getDownloads(Content dataSource, IngestJobContext context, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
352  if (file.getSize() == 0) {
353  return;
354  }
355 
356  File tempFile = createTemporaryFile(context, file);
357 
358  try {
359  Collection<BlackboardArtifact> bbartifacts = getDownloadArtifacts(dataSource, file, tempFile);
360  if (!bbartifacts.isEmpty()) {
361  services.fireModuleDataEvent(new ModuleDataEvent(
362  RecentActivityExtracterModuleFactory.getModuleName(),
363  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
364  }
365  } finally {
366  if (tempFile != null) {
367  tempFile.delete();
368  }
369  }
370 
371  }
372 
382  private void getCookies(IngestJobContext context, AbstractFile file) throws TskCoreException, IOException {
383  if (file.getSize() == 0) {
384  return;
385  }
386 
387  File tempFile = null;
388 
389  try {
390  tempFile = createTemporaryFile(context, file);
391 
392  Collection<BlackboardArtifact> bbartifacts = getCookieArtifacts(file, tempFile, context);
393 
394  if (!bbartifacts.isEmpty()) {
395  services.fireModuleDataEvent(new ModuleDataEvent(
396  RecentActivityExtracterModuleFactory.getModuleName(),
397  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, bbartifacts));
398  }
399  } finally {
400  if (tempFile != null) {
401  tempFile.delete();
402  }
403  }
404  }
405 
416  private Collection<BlackboardArtifact> getHistoryArtifacts(AbstractFile origFile, Path tempFilePath, IngestJobContext context) throws TskCoreException {
417  List<HashMap<String, Object>> historyList = this.dbConnect(tempFilePath.toString(), HISTORY_QUERY);
418 
419  if (historyList == null || historyList.isEmpty()) {
420  return null;
421  }
422 
423  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
424  for (HashMap<String, Object> row : historyList) {
425  if (context.dataSourceIngestIsCancelled()) {
426  return bbartifacts;
427  }
428 
429  String url = row.get(HEAD_URL).toString();
430  String title = row.get(HEAD_TITLE).toString();
431  Long time = (Double.valueOf(row.get(HEAD_TIME).toString())).longValue();
432 
433  BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
434  bbart.addAttributes(createHistoryAttribute(url, time, null, title,
435  this.getName(), NetworkUtils.extractDomain(url), null));
436  bbartifacts.add(bbart);
437  }
438 
439  return bbartifacts;
440  }
441 
455  private Collection<BlackboardArtifact> getBookmarkArtifacts(AbstractFile origFile, File tempFile, IngestJobContext context) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
456  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
457 
458  try {
459  NSDictionary root = (NSDictionary) PropertyListParser.parse(tempFile);
460 
461  parseBookmarkDictionary(bbartifacts, origFile, root, context);
462  } catch (PropertyListFormatException ex) {
463  PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
464  plfe.setStackTrace(ex.getStackTrace());
465  throw plfe;
466  } catch (ParseException ex) {
467  ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
468  pe.setStackTrace(ex.getStackTrace());
469  throw pe;
470  } catch (ParserConfigurationException ex) {
471  ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
472  pce.setStackTrace(ex.getStackTrace());
473  throw pce;
474  } catch (SAXException ex) {
475  SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
476  se.setStackTrace(ex.getStackTrace());
477  throw se;
478  }
479 
480  return bbartifacts;
481  }
482 
496  private Collection<BlackboardArtifact> getDownloadArtifacts(Content dataSource, AbstractFile origFile, File tempFile)throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
497  Collection<BlackboardArtifact> bbartifacts = null;
498 
499  try {
500  while(true){
501  NSDictionary root = (NSDictionary)PropertyListParser.parse(tempFile);
502 
503  if(root == null)
504  break;
505 
506  NSArray nsArray = (NSArray)root.get(PLIST_KEY_DOWNLOAD_HISTORY);
507 
508  if(nsArray == null)
509  break;
510 
511  NSObject[] objectArray = nsArray.getArray();
512  bbartifacts = new ArrayList<>();
513 
514  for(NSObject obj: objectArray){
515  if(obj instanceof NSDictionary){
516  bbartifacts.addAll(parseDownloadDictionary(dataSource, origFile, (NSDictionary)obj));
517  }
518  }
519  break;
520  }
521 
522  } catch (PropertyListFormatException ex) {
523  PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
524  plfe.setStackTrace(ex.getStackTrace());
525  throw plfe;
526  } catch (ParseException ex) {
527  ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
528  pe.setStackTrace(ex.getStackTrace());
529  throw pe;
530  } catch (ParserConfigurationException ex) {
531  ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
532  pce.setStackTrace(ex.getStackTrace());
533  throw pce;
534  } catch (SAXException ex) {
535  SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
536  se.setStackTrace(ex.getStackTrace());
537  throw se;
538  }
539 
540  return bbartifacts;
541  }
542 
553  private Collection<BlackboardArtifact> getCookieArtifacts(AbstractFile origFile, File tempFile, IngestJobContext context) throws TskCoreException, IOException {
554  Collection<BlackboardArtifact> bbartifacts = null;
555  BinaryCookieReader reader = BinaryCookieReader.initalizeReader(tempFile);
556 
557  if (reader != null) {
558  bbartifacts = new ArrayList<>();
559 
560  Iterator<Cookie> iter = reader.iterator();
561  while (iter.hasNext()) {
562  if (context.dataSourceIngestIsCancelled()) {
563  return bbartifacts;
564  }
565 
566  Cookie cookie = iter.next();
567 
568  BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
569  bbart.addAttributes(createCookieAttributes(cookie.getURL(), cookie.getCreationDate(), cookie.getName(), cookie.getValue(), this.getName(), NetworkUtils.extractDomain(cookie.getURL())));
570  bbartifacts.add(bbart);
571  }
572  }
573 
574  return bbartifacts;
575  }
576 
586  private void parseBookmarkDictionary(Collection<BlackboardArtifact> bbartifacts, AbstractFile origFile, NSDictionary root, IngestJobContext context) throws TskCoreException {
587 
588  if (context.dataSourceIngestIsCancelled()) {
589  return;
590  }
591 
592  if (root.containsKey(PLIST_KEY_CHILDREN)) {
593  NSArray children = (NSArray) root.objectForKey(PLIST_KEY_CHILDREN);
594 
595  if (children != null) {
596  for (NSObject obj : children.getArray()) {
597  parseBookmarkDictionary(bbartifacts, origFile, (NSDictionary) obj, context);
598  }
599  }
600  } else if (root.containsKey(PLIST_KEY_URL)) {
601  String url = null;
602  String title = null;
603 
604  NSString nsstr = (NSString) root.objectForKey(PLIST_KEY_URL);
605  if (nsstr != null) {
606  url = nsstr.toString();
607  }
608 
609  NSDictionary dic = (NSDictionary) root.get(PLIST_KEY_URI);
610 
611  nsstr = (NSString) root.objectForKey(PLIST_KEY_TITLE);
612 
613  if (nsstr != null) {
614  title = ((NSString) dic.get(PLIST_KEY_TITLE)).toString();
615  }
616 
617  if (url != null || title != null) {
618  BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
619  bbart.addAttributes(createBookmarkAttributes(url, title, null, getName(), NetworkUtils.extractDomain(url)));
620  bbartifacts.add(bbart);
621  }
622  }
623  }
624 
634  private Collection<BlackboardArtifact> parseDownloadDictionary(Content dataSource, AbstractFile origFile, NSDictionary entry) throws TskCoreException {
635  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
636  String url = null;
637  String path = null;
638  Long time = null;
639  Long pathID = null;
640 
641  FileManager fileManager = getCurrentCase().getServices().getFileManager();
642 
643  NSString nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_URL);
644  if (nsstring != null) {
645  url = nsstring.toString();
646  }
647 
648  nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_PATH);
649  if (nsstring != null) {
650  path = nsstring.toString();
651  pathID = Util.findID(dataSource, path);
652  }
653 
654  NSDate date = (NSDate) entry.get(PLIST_KEY_DOWNLOAD_DATE);
655  if (date != null) {
656  time = date.getDate().getTime();
657  }
658 
659  BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD);
660  bbart.addAttributes(this.createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getName()));
661  bbartifacts.add(bbart);
662 
663  // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it.
664  for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(path), FilenameUtils.getPath(path))) {
665  BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
666  downloadSourceArt.addAttributes(createDownloadSourceAttributes(url));
667  bbartifacts.add(downloadSourceArt);
668  break;
669  }
670 
671  return bbartifacts;
672  }
673 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Jun 21 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.