Autopsy  4.19.1
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-2021 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;
50 import org.sleuthkit.datamodel.AbstractFile;
51 import org.sleuthkit.datamodel.BlackboardArtifact;
52 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK;
53 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE;
54 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
55 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
56 import org.sleuthkit.datamodel.Content;
57 import org.sleuthkit.datamodel.TskCoreException;
58 import org.xml.sax.SAXException;
59 
64 final class ExtractSafari extends Extract {
65 
66  private final IngestServices services = IngestServices.getInstance();
67 
68  // visit_time uses an epoch of Jan 1, 2001 thus the addition of 978307200
69  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
70 
71  private static final String HISTORY_FILE_NAME = "History.db"; //NON-NLS
72  private static final String BOOKMARK_FILE_NAME = "Bookmarks.plist"; //NON-NLS
73  private static final String DOWNLOAD_FILE_NAME = "Downloads.plist"; //NON-NLS
74  private static final String COOKIE_FILE_NAME = "Cookies.binarycookies"; //NON-NLS
75  private static final String COOKIE_FOLDER = "Cookies";
76  private static final String SAFARI_FOLDER = "Safari";
77 
78  private static final String HEAD_URL = "url"; //NON-NLS
79  private static final String HEAD_TITLE = "title"; //NON-NLS
80  private static final String HEAD_TIME = "time"; //NON-NLS
81 
82  private static final String PLIST_KEY_CHILDREN = "Children"; //NON-NLS
83  private static final String PLIST_KEY_URL = "URLString"; //NON-NLS
84  private static final String PLIST_KEY_URI = "URIDictionary"; //NON-NLS
85  private static final String PLIST_KEY_TITLE = "title"; //NON-NLS
86  private static final String PLIST_KEY_DOWNLOAD_URL = "DownloadEntryURL"; //NON-NLS
87  private static final String PLIST_KEY_DOWNLOAD_DATE = "DownloadEntryDateAddedKey"; //NON-NLS
88  private static final String PLIST_KEY_DOWNLOAD_PATH = "DownloadEntryPath"; //NON-NLS
89  private static final String PLIST_KEY_DOWNLOAD_HISTORY = "DownloadHistory"; //NON-NLS
90 
91  private static final Logger LOG = Logger.getLogger(ExtractSafari.class.getName());
92 
93  @Messages({
94  "ExtractSafari_Module_Name=Safari",
95  "ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files.",
96  "ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files",
97  "ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files",
98  "Progress_Message_Safari_History=Safari History",
99  "Progress_Message_Safari_Bookmarks=Safari Bookmarks",
100  "Progress_Message_Safari_Cookies=Safari Cookies",
101  "Progress_Message_Safari_Downloads=Safari Downloads",
102  })
103 
104  @Override
105  protected String getName() {
106  return Bundle.ExtractSafari_Module_Name();
107  }
108 
109  @Override
110  void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
111  setFoundData(false);
112 
113  progressBar.progress(Bundle.Progress_Message_Safari_Cookies());
114  try {
115  processHistoryDB(dataSource, context);
116 
117  } catch (IOException | TskCoreException ex) {
118  this.addErrorMessage(Bundle.ExtractSafari_Error_Getting_History());
119  LOG.log(Level.SEVERE, "Exception thrown while processing history file.", ex); //NON-NLS
120  }
121 
122  if (context.dataSourceIngestIsCancelled()) {
123  return;
124  }
125 
126  progressBar.progress(Bundle.Progress_Message_Safari_Bookmarks());
127  try {
128  processBookmarkPList(dataSource, context);
129  } catch (IOException | TskCoreException | SAXException | PropertyListFormatException | ParseException | ParserConfigurationException ex) {
130  this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Bookmark());
131  LOG.log(Level.SEVERE, "Exception thrown while parsing Safari Bookmarks file.", ex); //NON-NLS
132  }
133 
134  if (context.dataSourceIngestIsCancelled()) {
135  return;
136  }
137 
138  progressBar.progress(Bundle.Progress_Message_Safari_Downloads());
139  try {
140  processDownloadsPList(dataSource, context);
141  } catch (IOException | TskCoreException | SAXException | PropertyListFormatException | ParseException | ParserConfigurationException ex) {
142  this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Bookmark());
143  LOG.log(Level.SEVERE, "Exception thrown while parsing Safari Download.plist file.", ex); //NON-NLS
144  }
145 
146  if (context.dataSourceIngestIsCancelled()) {
147  return;
148  }
149 
150  progressBar.progress(Bundle.Progress_Message_Safari_Cookies());
151  try {
152  processBinaryCookieFile(dataSource, context);
153  } catch (TskCoreException ex) {
154  this.addErrorMessage(Bundle.ExtractSafari_Error_Parsing_Cookies());
155  LOG.log(Level.SEVERE, "Exception thrown while processing Safari cookies file.", ex); //NON-NLS
156  }
157  }
158 
166  private void processHistoryDB(Content dataSource, IngestJobContext context) throws TskCoreException, IOException {
167  FileManager fileManager = getCurrentCase().getServices().getFileManager();
168 
169  List<AbstractFile> historyFiles = fileManager.findFiles(dataSource, HISTORY_FILE_NAME, SAFARI_FOLDER);
170 
171  if (historyFiles == null || historyFiles.isEmpty()) {
172  return;
173  }
174 
175  setFoundData(true);
176 
177  for (AbstractFile historyFile : historyFiles) {
178  if (context.dataSourceIngestIsCancelled()) {
179  break;
180  }
181 
182  getHistory(context, historyFile);
183  }
184  }
185 
197  private void processBookmarkPList(Content dataSource, IngestJobContext context) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
198  FileManager fileManager = getCurrentCase().getServices().getFileManager();
199 
200  List<AbstractFile> files = fileManager.findFiles(dataSource, BOOKMARK_FILE_NAME, SAFARI_FOLDER);
201 
202  if (files == null || files.isEmpty()) {
203  return;
204  }
205 
206  setFoundData(true);
207 
208  for (AbstractFile file : files) {
209  if (context.dataSourceIngestIsCancelled()) {
210  break;
211  }
212 
213  getBookmarks(context, file);
214  }
215  }
216 
229  private void processDownloadsPList(Content dataSource, IngestJobContext context) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
230  FileManager fileManager = getCurrentCase().getServices().getFileManager();
231 
232  List<AbstractFile> files = fileManager.findFiles(dataSource, DOWNLOAD_FILE_NAME, SAFARI_FOLDER);
233 
234  if (files == null || files.isEmpty()) {
235  return;
236  }
237 
238  setFoundData(true);
239 
240  for (AbstractFile file : files) {
241  if (context.dataSourceIngestIsCancelled()) {
242  break;
243  }
244 
245  getDownloads(dataSource, context, file);
246  }
247  }
248 
256  private void processBinaryCookieFile(Content dataSource, IngestJobContext context) throws TskCoreException {
257  FileManager fileManager = getCurrentCase().getServices().getFileManager();
258 
259  List<AbstractFile> files = fileManager.findFiles(dataSource, COOKIE_FILE_NAME, COOKIE_FOLDER);
260 
261  if (files == null || files.isEmpty()) {
262  return;
263  }
264 
265  setFoundData(true);
266 
267  for (AbstractFile file : files) {
268  if (context.dataSourceIngestIsCancelled()) {
269  break;
270  }
271  try {
272  getCookies(context, file);
273  } catch (IOException ex) {
274  LOG.log(Level.WARNING, String.format("Failed to get cookies from file %s", Paths.get(file.getUniquePath(), file.getName()).toString()), ex);
275  }
276  }
277  }
278 
287  private void getHistory(IngestJobContext context, AbstractFile historyFile) throws TskCoreException, IOException {
288  if (historyFile.getSize() == 0) {
289  return;
290  }
291 
292  File tempHistoryFile = createTemporaryFile(context, historyFile, context.getJobId());
293 
294  try {
295  ContentUtils.writeToFile(historyFile, tempHistoryFile, context::dataSourceIngestIsCancelled);
296  } catch (IOException ex) {
297  throw new IOException("Error writingToFile: " + historyFile, ex); //NON-NLS
298  }
299 
300  try {
301  if(!context.dataSourceIngestIsCancelled()) {
302  postArtifacts(getHistoryArtifacts(historyFile, tempHistoryFile.toPath(), context));
303  }
304  } finally {
305  tempHistoryFile.delete();
306  }
307  }
308 
322  private void getBookmarks(IngestJobContext context, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
323  if (file.getSize() == 0) {
324  return;
325  }
326 
327  File tempFile = createTemporaryFile(context, file, context.getJobId());
328 
329  try {
330  if(!context.dataSourceIngestIsCancelled()) {
331  postArtifacts(getBookmarkArtifacts(file, tempFile, context));
332  }
333  } finally {
334  tempFile.delete();
335  }
336 
337  }
338 
352  private void getDownloads(Content dataSource, IngestJobContext context, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
353  if (file.getSize() == 0) {
354  return;
355  }
356 
357  File tempFile = createTemporaryFile(context, file, context.getJobId());
358 
359  try {
360  if(!context.dataSourceIngestIsCancelled()) {
361  postArtifacts(getDownloadArtifacts(dataSource, file, tempFile));
362  }
363  } finally {
364  if (tempFile != null) {
365  tempFile.delete();
366  }
367  }
368 
369  }
370 
380  private void getCookies(IngestJobContext context, AbstractFile file) throws TskCoreException, IOException {
381  if (file.getSize() == 0) {
382  return;
383  }
384 
385  File tempFile = null;
386 
387  try {
388  tempFile = createTemporaryFile(context, file, context.getJobId());
389 
390  if(!context.dataSourceIngestIsCancelled()) {
391  postArtifacts(getCookieArtifacts(file, tempFile, context));
392  }
393 
394  } finally {
395  if (tempFile != null) {
396  tempFile.delete();
397  }
398  }
399  }
400 
411  private Collection<BlackboardArtifact> getHistoryArtifacts(AbstractFile origFile, Path tempFilePath, IngestJobContext context) throws TskCoreException {
412  List<HashMap<String, Object>> historyList = this.dbConnect(tempFilePath.toString(), HISTORY_QUERY);
413 
414  if (historyList == null || historyList.isEmpty()) {
415  return null;
416  }
417 
418  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
419  for (HashMap<String, Object> row : historyList) {
420  if (context.dataSourceIngestIsCancelled()) {
421  return bbartifacts;
422  }
423 
424  String url = row.get(HEAD_URL).toString();
425  String title = row.get(HEAD_TITLE).toString();
426  Long time = (Double.valueOf(row.get(HEAD_TIME).toString())).longValue();
427 
428  bbartifacts.add(
429  createArtifactWithAttributes(
430  TSK_WEB_HISTORY,
431  origFile,
432  createHistoryAttribute(url, time, null, title,
433  this.getName(), NetworkUtils.extractDomain(url), null)));
434  }
435 
436  return bbartifacts;
437  }
438 
452  private Collection<BlackboardArtifact> getBookmarkArtifacts(AbstractFile origFile, File tempFile, IngestJobContext context) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
453  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
454 
455  try {
456  NSDictionary root = (NSDictionary) PropertyListParser.parse(tempFile);
457 
458  parseBookmarkDictionary(bbartifacts, origFile, root, context);
459  } catch (PropertyListFormatException ex) {
460  PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
461  plfe.setStackTrace(ex.getStackTrace());
462  throw plfe;
463  } catch (ParseException ex) {
464  ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
465  pe.setStackTrace(ex.getStackTrace());
466  throw pe;
467  } catch (ParserConfigurationException ex) {
468  ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
469  pce.setStackTrace(ex.getStackTrace());
470  throw pce;
471  } catch (SAXException ex) {
472  SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
473  se.setStackTrace(ex.getStackTrace());
474  throw se;
475  }
476 
477  return bbartifacts;
478  }
479 
493  private Collection<BlackboardArtifact> getDownloadArtifacts(Content dataSource, AbstractFile origFile, File tempFile)throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
494  Collection<BlackboardArtifact> bbartifacts = null;
495 
496  try {
497  while(true){
498  NSDictionary root = (NSDictionary)PropertyListParser.parse(tempFile);
499 
500  if(root == null)
501  break;
502 
503  NSArray nsArray = (NSArray)root.get(PLIST_KEY_DOWNLOAD_HISTORY);
504 
505  if(nsArray == null)
506  break;
507 
508  NSObject[] objectArray = nsArray.getArray();
509  bbartifacts = new ArrayList<>();
510 
511  for(NSObject obj: objectArray){
512  if(obj instanceof NSDictionary){
513  bbartifacts.addAll(parseDownloadDictionary(dataSource, origFile, (NSDictionary)obj));
514  }
515  }
516  break;
517  }
518 
519  } catch (PropertyListFormatException ex) {
520  PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
521  plfe.setStackTrace(ex.getStackTrace());
522  throw plfe;
523  } catch (ParseException ex) {
524  ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
525  pe.setStackTrace(ex.getStackTrace());
526  throw pe;
527  } catch (ParserConfigurationException ex) {
528  ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
529  pce.setStackTrace(ex.getStackTrace());
530  throw pce;
531  } catch (SAXException ex) {
532  SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
533  se.setStackTrace(ex.getStackTrace());
534  throw se;
535  }
536 
537  return bbartifacts;
538  }
539 
550  private Collection<BlackboardArtifact> getCookieArtifacts(AbstractFile origFile, File tempFile, IngestJobContext context) throws TskCoreException, IOException {
551  Collection<BlackboardArtifact> bbartifacts = null;
552  BinaryCookieReader reader = BinaryCookieReader.initalizeReader(tempFile);
553 
554  if (reader != null) {
555  bbartifacts = new ArrayList<>();
556 
557  Iterator<Cookie> iter = reader.iterator();
558  while (iter.hasNext()) {
559  if (context.dataSourceIngestIsCancelled()) {
560  return bbartifacts;
561  }
562 
563  Cookie cookie = iter.next();
564 
565  bbartifacts.add(
566  createArtifactWithAttributes(
567  TSK_WEB_COOKIE,
568  origFile,
569  createCookieAttributes(
570  cookie.getURL(),
571  cookie.getCreationDate(),
572  null,
573  cookie.getExpirationDate(),
574  cookie.getName(), cookie.getValue(),
575  this.getName(),
576  NetworkUtils.extractDomain(cookie.getURL()))));
577  }
578  }
579 
580  return bbartifacts;
581  }
582 
592  private void parseBookmarkDictionary(Collection<BlackboardArtifact> bbartifacts, AbstractFile origFile, NSDictionary root, IngestJobContext context) throws TskCoreException {
593 
594  if (context.dataSourceIngestIsCancelled()) {
595  return;
596  }
597 
598  if (root.containsKey(PLIST_KEY_CHILDREN)) {
599  NSArray children = (NSArray) root.objectForKey(PLIST_KEY_CHILDREN);
600 
601  if (children != null) {
602  for (NSObject obj : children.getArray()) {
603  parseBookmarkDictionary(bbartifacts, origFile, (NSDictionary) obj, context);
604  }
605  }
606  } else if (root.containsKey(PLIST_KEY_URL)) {
607  String url = null;
608  String title = null;
609 
610  NSString nsstr = (NSString) root.objectForKey(PLIST_KEY_URL);
611  if (nsstr != null) {
612  url = nsstr.toString();
613  }
614 
615  NSDictionary dic = (NSDictionary) root.get(PLIST_KEY_URI);
616 
617  nsstr = (NSString) root.objectForKey(PLIST_KEY_TITLE);
618 
619  if (nsstr != null) {
620  title = ((NSString) dic.get(PLIST_KEY_TITLE)).toString();
621  }
622 
623  if (url != null || title != null) {
624  bbartifacts.add(createArtifactWithAttributes(TSK_WEB_BOOKMARK, origFile,
625  createBookmarkAttributes(url,
626  title,
627  null,
628  getName(),
629  NetworkUtils.extractDomain(url))));
630  }
631  }
632  }
633 
643  private Collection<BlackboardArtifact> parseDownloadDictionary(Content dataSource, AbstractFile origFile, NSDictionary entry) throws TskCoreException {
644  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
645  String url = null;
646  String path = null;
647  Long time = null;
648  Long pathID = null;
649 
650  NSString nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_URL);
651  if (nsstring != null) {
652  url = nsstring.toString();
653  }
654 
655  nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_PATH);
656  if (nsstring != null) {
657  path = nsstring.toString();
658  pathID = Util.findID(dataSource, path);
659  }
660 
661  NSDate date = (NSDate) entry.get(PLIST_KEY_DOWNLOAD_DATE);
662  if (date != null) {
663  time = date.getDate().getTime();
664  }
665 
666  BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(TSK_WEB_DOWNLOAD, origFile, createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getName()));
667  bbartifacts.add(webDownloadArtifact);
668 
669  // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
670  for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource,
671  FilenameUtils.getName(path), FilenameUtils.getPath(path))) {
672  bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact));
673  break;
674  }
675 
676  return bbartifacts;
677  }
678 }

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.