Autopsy  4.19.3
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  private final IngestJobContext context;
68 
69  // visit_time uses an epoch of Jan 1, 2001 thus the addition of 978307200
70  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
71 
72  private static final String HISTORY_FILE_NAME = "History.db"; //NON-NLS
73  private static final String BOOKMARK_FILE_NAME = "Bookmarks.plist"; //NON-NLS
74  private static final String DOWNLOAD_FILE_NAME = "Downloads.plist"; //NON-NLS
75  private static final String COOKIE_FILE_NAME = "Cookies.binarycookies"; //NON-NLS
76  private static final String COOKIE_FOLDER = "Cookies";
77  private static final String SAFARI_FOLDER = "Safari";
78 
79  private static final String HEAD_URL = "url"; //NON-NLS
80  private static final String HEAD_TITLE = "title"; //NON-NLS
81  private static final String HEAD_TIME = "time"; //NON-NLS
82 
83  private static final String PLIST_KEY_CHILDREN = "Children"; //NON-NLS
84  private static final String PLIST_KEY_URL = "URLString"; //NON-NLS
85  private static final String PLIST_KEY_URI = "URIDictionary"; //NON-NLS
86  private static final String PLIST_KEY_TITLE = "title"; //NON-NLS
87  private static final String PLIST_KEY_DOWNLOAD_URL = "DownloadEntryURL"; //NON-NLS
88  private static final String PLIST_KEY_DOWNLOAD_DATE = "DownloadEntryDateAddedKey"; //NON-NLS
89  private static final String PLIST_KEY_DOWNLOAD_PATH = "DownloadEntryPath"; //NON-NLS
90  private static final String PLIST_KEY_DOWNLOAD_HISTORY = "DownloadHistory"; //NON-NLS
91 
92  private static final Logger LOG = Logger.getLogger(ExtractSafari.class.getName());
93 
94  @Messages({
95  "ExtractSafari_Module_Name=Safari Analyzer",
96  "ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files.",
97  "ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files",
98  "ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files",
99  "Progress_Message_Safari_History=Safari History",
100  "Progress_Message_Safari_Bookmarks=Safari Bookmarks",
101  "Progress_Message_Safari_Cookies=Safari Cookies",
102  "Progress_Message_Safari_Downloads=Safari Downloads",})
103 
104  ExtractSafari(IngestJobContext context) {
105  super(Bundle.ExtractSafari_Module_Name(), context);
106  this.context = context;
107  }
108 
109  @Override
110  void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
111  setFoundData(false);
112 
113  progressBar.progress(Bundle.Progress_Message_Safari_Cookies());
114  try {
115  processHistoryDB(dataSource);
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);
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);
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);
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) 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(historyFile);
183  }
184  }
185 
199  private void processBookmarkPList(Content dataSource) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
200  FileManager fileManager = getCurrentCase().getServices().getFileManager();
201 
202  List<AbstractFile> files = fileManager.findFiles(dataSource, BOOKMARK_FILE_NAME, SAFARI_FOLDER);
203 
204  if (files == null || files.isEmpty()) {
205  return;
206  }
207 
208  setFoundData(true);
209 
210  for (AbstractFile file : files) {
211  if (context.dataSourceIngestIsCancelled()) {
212  break;
213  }
214 
215  getBookmarks(file);
216  }
217  }
218 
232  private void processDownloadsPList(Content dataSource) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
233  FileManager fileManager = getCurrentCase().getServices().getFileManager();
234 
235  List<AbstractFile> files = fileManager.findFiles(dataSource, DOWNLOAD_FILE_NAME, SAFARI_FOLDER);
236 
237  if (files == null || files.isEmpty()) {
238  return;
239  }
240 
241  setFoundData(true);
242 
243  for (AbstractFile file : files) {
244  if (context.dataSourceIngestIsCancelled()) {
245  break;
246  }
247 
248  getDownloads(dataSource, file);
249  }
250  }
251 
261  private void processBinaryCookieFile(Content dataSource) throws TskCoreException {
262  FileManager fileManager = getCurrentCase().getServices().getFileManager();
263 
264  List<AbstractFile> files = fileManager.findFiles(dataSource, COOKIE_FILE_NAME, COOKIE_FOLDER);
265 
266  if (files == null || files.isEmpty()) {
267  return;
268  }
269 
270  setFoundData(true);
271 
272  for (AbstractFile file : files) {
273  if (context.dataSourceIngestIsCancelled()) {
274  break;
275  }
276  try {
277  getCookies(file);
278  } catch (IOException ex) {
279  LOG.log(Level.WARNING, String.format("Failed to get cookies from file %s", Paths.get(file.getUniquePath(), file.getName()).toString()), ex);
280  }
281  }
282  }
283 
293  private void getHistory(AbstractFile historyFile) throws TskCoreException, IOException {
294  if (historyFile.getSize() == 0) {
295  return;
296  }
297  File tempHistoryFile = createTemporaryFile(historyFile);
298  try {
299  postArtifacts(getHistoryArtifacts(historyFile, tempHistoryFile.toPath()));
300  } finally {
301  tempHistoryFile.delete();
302  }
303  }
304 
318  private void getBookmarks(AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
319  if (file.getSize() == 0) {
320  return;
321  }
322  File tempFile = createTemporaryFile(file);
323  try {
324  postArtifacts(getBookmarkArtifacts(file, tempFile));
325  } finally {
326  tempFile.delete();
327  }
328  }
329 
343  private void getDownloads(Content dataSource, AbstractFile file) throws TskCoreException, IOException, SAXException, PropertyListFormatException, ParseException, ParserConfigurationException {
344  if (file.getSize() == 0) {
345  return;
346  }
347  File tempFile = createTemporaryFile(file);
348  try {
349  postArtifacts(getDownloadArtifacts(dataSource, file, tempFile));
350  } finally {
351  tempFile.delete();
352  }
353  }
354 
364  private void getCookies(AbstractFile file) throws TskCoreException, IOException {
365  if (file.getSize() == 0) {
366  return;
367  }
368 
369  File tempFile = null;
370 
371  try {
372  tempFile = createTemporaryFile(file);
373 
374  if (!context.dataSourceIngestIsCancelled()) {
375  postArtifacts(getCookieArtifacts(file, tempFile));
376  }
377 
378  } finally {
379  if (tempFile != null) {
380  tempFile.delete();
381  }
382  }
383  }
384 
397  private Collection<BlackboardArtifact> getHistoryArtifacts(AbstractFile origFile, Path tempFilePath) throws TskCoreException {
398  List<HashMap<String, Object>> historyList = this.querySQLiteDb(tempFilePath.toString(), HISTORY_QUERY);
399 
400  if (historyList == null || historyList.isEmpty()) {
401  return null;
402  }
403 
404  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
405  for (HashMap<String, Object> row : historyList) {
406  if (context.dataSourceIngestIsCancelled()) {
407  return bbartifacts;
408  }
409 
410  String url = row.get(HEAD_URL).toString();
411  String title = row.get(HEAD_TITLE).toString();
412  Long time = (Double.valueOf(row.get(HEAD_TIME).toString())).longValue();
413 
414  bbartifacts.add(
415  createArtifactWithAttributes(
416  BlackboardArtifact.Type.TSK_WEB_HISTORY,
417  origFile,
418  createHistoryAttributes(url, time, null, title,
419  this.getDisplayName(), NetworkUtils.extractDomain(url), null)));
420  }
421 
422  return bbartifacts;
423  }
424 
440  private Collection<BlackboardArtifact> getBookmarkArtifacts(AbstractFile origFile, File tempFile) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
441  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
442 
443  try {
444  NSDictionary root = (NSDictionary) PropertyListParser.parse(tempFile);
445 
446  parseBookmarkDictionary(bbartifacts, origFile, root);
447  } catch (PropertyListFormatException ex) {
448  PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
449  plfe.setStackTrace(ex.getStackTrace());
450  throw plfe;
451  } catch (ParseException ex) {
452  ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
453  pe.setStackTrace(ex.getStackTrace());
454  throw pe;
455  } catch (ParserConfigurationException ex) {
456  ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
457  pce.setStackTrace(ex.getStackTrace());
458  throw pce;
459  } catch (SAXException ex) {
460  SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
461  se.setStackTrace(ex.getStackTrace());
462  throw se;
463  }
464 
465  return bbartifacts;
466  }
467 
484  private Collection<BlackboardArtifact> getDownloadArtifacts(Content dataSource, AbstractFile origFile, File tempFile) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException, TskCoreException {
485  Collection<BlackboardArtifact> bbartifacts = null;
486 
487  try {
488  while (true) {
489  NSDictionary root = (NSDictionary) PropertyListParser.parse(tempFile);
490 
491  if (root == null) {
492  break;
493  }
494 
495  NSArray nsArray = (NSArray) root.get(PLIST_KEY_DOWNLOAD_HISTORY);
496 
497  if (nsArray == null) {
498  break;
499  }
500 
501  NSObject[] objectArray = nsArray.getArray();
502  bbartifacts = new ArrayList<>();
503 
504  for (NSObject obj : objectArray) {
505  if (obj instanceof NSDictionary) {
506  bbartifacts.addAll(parseDownloadDictionary(dataSource, origFile, (NSDictionary) obj));
507  }
508  }
509  break;
510  }
511 
512  } catch (PropertyListFormatException ex) {
513  PropertyListFormatException plfe = new PropertyListFormatException(origFile.getName() + ": " + ex.getMessage());
514  plfe.setStackTrace(ex.getStackTrace());
515  throw plfe;
516  } catch (ParseException ex) {
517  ParseException pe = new ParseException(origFile.getName() + ": " + ex.getMessage(), ex.getErrorOffset());
518  pe.setStackTrace(ex.getStackTrace());
519  throw pe;
520  } catch (ParserConfigurationException ex) {
521  ParserConfigurationException pce = new ParserConfigurationException(origFile.getName() + ": " + ex.getMessage());
522  pce.setStackTrace(ex.getStackTrace());
523  throw pce;
524  } catch (SAXException ex) {
525  SAXException se = new SAXException(origFile.getName() + ": " + ex.getMessage());
526  se.setStackTrace(ex.getStackTrace());
527  throw se;
528  }
529 
530  return bbartifacts;
531  }
532 
545  private Collection<BlackboardArtifact> getCookieArtifacts(AbstractFile origFile, File tempFile) throws TskCoreException, IOException {
546  Collection<BlackboardArtifact> bbartifacts = null;
547  BinaryCookieReader reader = BinaryCookieReader.initalizeReader(tempFile);
548 
549  if (reader != null) {
550  bbartifacts = new ArrayList<>();
551 
552  Iterator<Cookie> iter = reader.iterator();
553  while (iter.hasNext()) {
554  if (context.dataSourceIngestIsCancelled()) {
555  return bbartifacts;
556  }
557 
558  Cookie cookie = iter.next();
559 
560  bbartifacts.add(
561  createArtifactWithAttributes(
562  BlackboardArtifact.Type.TSK_WEB_COOKIE,
563  origFile,
564  createCookieAttributes(
565  cookie.getURL(),
566  cookie.getCreationDate(),
567  null,
568  cookie.getExpirationDate(),
569  cookie.getName(), cookie.getValue(),
570  this.getDisplayName(),
571  NetworkUtils.extractDomain(cookie.getURL()))));
572  }
573  }
574 
575  return bbartifacts;
576  }
577 
588  private void parseBookmarkDictionary(Collection<BlackboardArtifact> bbartifacts, AbstractFile origFile, NSDictionary root) throws TskCoreException {
589 
590  if (context.dataSourceIngestIsCancelled()) {
591  return;
592  }
593 
594  if (root.containsKey(PLIST_KEY_CHILDREN)) {
595  NSArray children = (NSArray) root.objectForKey(PLIST_KEY_CHILDREN);
596 
597  if (children != null) {
598  for (NSObject obj : children.getArray()) {
599  parseBookmarkDictionary(bbartifacts, origFile, (NSDictionary) obj);
600  }
601  }
602  } else if (root.containsKey(PLIST_KEY_URL)) {
603  String url = null;
604  String title = null;
605 
606  NSString nsstr = (NSString) root.objectForKey(PLIST_KEY_URL);
607  if (nsstr != null) {
608  url = nsstr.toString();
609  }
610 
611  NSDictionary dic = (NSDictionary) root.get(PLIST_KEY_URI);
612 
613  nsstr = (NSString) root.objectForKey(PLIST_KEY_TITLE);
614 
615  if (nsstr != null) {
616  title = ((NSString) dic.get(PLIST_KEY_TITLE)).toString();
617  }
618 
619  if (url != null || title != null) {
620  bbartifacts.add(createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, origFile,
621  createBookmarkAttributes(url,
622  title,
623  null,
624  getDisplayName(),
625  NetworkUtils.extractDomain(url))));
626  }
627  }
628  }
629 
641  private Collection<BlackboardArtifact> parseDownloadDictionary(Content dataSource, AbstractFile origFile, NSDictionary entry) throws TskCoreException {
642  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
643  String url = null;
644  String path = null;
645  Long time = null;
646  Long pathID = null;
647 
648  NSString nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_URL);
649  if (nsstring != null) {
650  url = nsstring.toString();
651  }
652 
653  nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_PATH);
654  if (nsstring != null) {
655  path = nsstring.toString();
656  pathID = Util.findID(dataSource, path);
657  }
658 
659  NSDate date = (NSDate) entry.get(PLIST_KEY_DOWNLOAD_DATE);
660  if (date != null) {
661  time = date.getDate().getTime();
662  }
663 
664  BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_DOWNLOAD, origFile, createDownloadAttributes(path, pathID, url, time, NetworkUtils.extractDomain(url), getDisplayName()));
665  bbartifacts.add(webDownloadArtifact);
666 
667  // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
668  for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource,
669  FilenameUtils.getName(path), FilenameUtils.getPath(path))) {
670  bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact));
671  break;
672  }
673 
674  return bbartifacts;
675  }
676 }

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.