Autopsy 4.22.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 */
19package org.sleuthkit.autopsy.recentactivity;
20
21import com.dd.plist.NSArray;
22import com.dd.plist.NSDate;
23import com.dd.plist.NSDictionary;
24import com.dd.plist.NSObject;
25import com.dd.plist.NSString;
26import com.dd.plist.PropertyListFormatException;
27import com.dd.plist.PropertyListParser;
28import java.io.File;
29import java.io.IOException;
30import java.nio.file.Path;
31import java.nio.file.Paths;
32import java.text.ParseException;
33import java.util.ArrayList;
34import java.util.Collection;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.List;
38import java.util.logging.Level;
39import javax.xml.parsers.ParserConfigurationException;
40import org.apache.commons.io.FilenameUtils;
41import org.openide.util.NbBundle.Messages;
42import org.sleuthkit.autopsy.casemodule.services.FileManager;
43import org.sleuthkit.autopsy.coreutils.Logger;
44import org.sleuthkit.autopsy.coreutils.NetworkUtils;
45import org.sleuthkit.autopsy.datamodel.ContentUtils;
46import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
47import org.sleuthkit.autopsy.ingest.IngestJobContext;
48import org.sleuthkit.autopsy.ingest.IngestServices;
49import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie;
50import org.sleuthkit.datamodel.AbstractFile;
51import org.sleuthkit.datamodel.BlackboardArtifact;
52import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK;
53import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE;
54import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
55import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
56import org.sleuthkit.datamodel.Content;
57import org.sleuthkit.datamodel.TskCoreException;
58import org.xml.sax.SAXException;
59
64final 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-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.