Autopsy  4.19.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractJumpLists.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2021 Basis Technology Corp.
6  *
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 package org.sleuthkit.autopsy.recentactivity;
21 
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.logging.Level;
30 import org.apache.poi.EmptyFileException;
31 import org.apache.poi.poifs.filesystem.DirectoryEntry;
32 import org.apache.poi.poifs.filesystem.DocumentEntry;
33 import org.apache.poi.poifs.filesystem.DocumentInputStream;
34 import org.apache.poi.poifs.filesystem.Entry;
35 import org.apache.poi.poifs.filesystem.NotOLE2FileException;
36 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
37 import org.openide.util.NbBundle.Messages;
49 import org.sleuthkit.datamodel.AbstractFile;
50 import org.sleuthkit.datamodel.Content;
51 import org.sleuthkit.datamodel.DerivedFile;
52 import org.sleuthkit.datamodel.TskCoreException;
53 import org.sleuthkit.datamodel.TskData;
54 
59 final class ExtractJumpLists extends Extract {
60 
61  private static final Logger logger = Logger.getLogger(ExtractJumpLists.class.getName());
62 
63  private IngestJobContext context;
64 
65  private static final String JUMPLIST_TSK_COMMENT = "Jumplist File";
66  private static final String RA_DIR_NAME = "RecentActivity"; //NON-NLS
67  private static final String AUTOMATIC_DESTINATIONS_FILE_DIRECTORY = "%/AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/";
68  private static final String JUMPLIST_DIR_NAME = "jumplists"; //NON-NLS
69  private static final String VERSION_NUMBER = "1.0.0"; //NON-NLS
70  private String moduleName;
71  private FileManager fileManager;
72  private final IngestServices services = IngestServices.getInstance();
73 
74  @Messages({
75  "Jumplist_module_name=Windows Jumplist Extractor",
76  "Jumplist_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis."
77  })
78  ExtractJumpLists() {
79  super(Bundle.Jumplist_module_name());
80  }
81 
82  @Override
83  void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
84 
85  this.context = context;
86  moduleName = Bundle.Jumplist_module_name();
87  fileManager = currentCase.getServices().getFileManager();
88  long ingestJobId = context.getJobId();
89 
90  String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), JUMPLIST_DIR_NAME , ingestJobId);
91  List<AbstractFile> jumpListFiles = extractJumplistFiles(dataSource, ingestJobId, baseRaTempPath);
92 
93  if (jumpListFiles.isEmpty()) {
94  return;
95  }
96 
97  if (context.dataSourceIngestIsCancelled()) {
98  return;
99  }
100 
101  List<AbstractFile> derivedFiles = new ArrayList<>();
102  String derivedPath = null;
103  String baseRaModPath = RAImageIngestModule.getRAOutputPath(Case.getCurrentCase(), JUMPLIST_DIR_NAME, ingestJobId);
104  for (AbstractFile jumplistFile : jumpListFiles) {
105  if (!jumplistFile.getName().toLowerCase().contains("-slack") && !jumplistFile.getName().equals("..") &&
106  !jumplistFile.getName().equals(".") && jumplistFile.getSize() > 0) {
107  String jlFile = Paths.get(baseRaTempPath, jumplistFile.getName() + "_" + jumplistFile.getId()).toString();
108  String moduleOutPath = baseRaModPath + File.separator + jumplistFile.getName() + "_" + jumplistFile.getId();
109  derivedPath = RA_DIR_NAME + File.separator + JUMPLIST_DIR_NAME + "_" + ingestJobId + File.separator + jumplistFile.getName() + "_" + jumplistFile.getId();
110  File jlDir = new File(moduleOutPath);
111  if (jlDir.exists() == false) {
112  boolean dirMade = jlDir.mkdirs();
113  if (!dirMade) {
114  logger.log(Level.WARNING, "Error creating directory to store Jumplist LNK files %s", moduleOutPath); //NON-NLS
115  continue;
116  }
117  }
118  derivedFiles.addAll(extractLnkFiles(jlFile, moduleOutPath, jumplistFile, derivedPath));
119  }
120  }
121 
122  // notify listeners of new files and schedule for analysis
123  progressBar.progress(String.format(Bundle.Jumplist_adding_extracted_files_msg(), derivedFiles.size()));
124  derivedFiles.forEach((derived) -> { services.fireModuleContentEvent(new ModuleContentEvent(derived)); });
125  context.addFilesToJob(derivedFiles);
126 
127  }
128 
134  private List<AbstractFile> extractJumplistFiles(Content dataSource, Long ingestJobId, String baseRaTempPath) {
135  List<AbstractFile> jumpListFiles = new ArrayList<>();;
136  List<AbstractFile> tempJumpListFiles = new ArrayList<>();;
137 
138  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
139 
140  try {
141  tempJumpListFiles = fileManager.findFiles(dataSource, "%", AUTOMATIC_DESTINATIONS_FILE_DIRECTORY); //NON-NLS
142  if (!tempJumpListFiles.isEmpty()) {
143  jumpListFiles.addAll(tempJumpListFiles);
144  }
145  } catch (TskCoreException ex) {
146  logger.log(Level.WARNING, "Unable to find jumplist files.", ex); //NON-NLS
147  return jumpListFiles; // No need to continue
148  }
149 
150  for (AbstractFile jumpListFile : jumpListFiles) {
151 
152  if (context.dataSourceIngestIsCancelled()) {
153  return jumpListFiles;
154  }
155 
156  if (!jumpListFile.getName().toLowerCase().contains("-slack") && !jumpListFile.getName().equals("..") &&
157  !jumpListFile.getName().equals(".") && jumpListFile.getSize() > 0) {
158  String fileName = jumpListFile.getName() + "_" + jumpListFile.getId();
159  String jlFile = Paths.get(baseRaTempPath, fileName).toString();
160  try {
161  ContentUtils.writeToFile(jumpListFile, new File(jlFile));
162  } catch (IOException ex) {
163  logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", fileName, jlFile), ex); //NON-NLS
164  }
165  }
166  }
167 
168  return jumpListFiles;
169 
170  }
171 
172  /*
173  * Read each jumplist file and extract the lnk files to moduleoutput
174  */
175  private List<DerivedFile> extractLnkFiles(String jumpListFile, String moduleOutPath, AbstractFile jumpListAbsFile, String derivedPath) {
176 
177  List<DerivedFile> derivedFiles = new ArrayList<>();
178  DerivedFile derivedFile;
179  String lnkFileName = "";
180 
181  try (POIFSFileSystem fs = new POIFSFileSystem(new File(jumpListFile))) {
182  DirectoryEntry root = fs.getRoot();
183  for (Entry entry : root) {
184  if (entry instanceof DirectoryEntry) {
185  //If this data structure needed to recurse this is where it would do it but jumplists do not need to at this time
186  continue;
187  } else if (entry instanceof DocumentEntry) {
188  String jmpListFileName = entry.getName();
189  int fileSize = ((DocumentEntry) entry).getSize();
190 
191  if (fileSize > 0) {
192  try (DocumentInputStream stream = fs.createDocumentInputStream(jmpListFileName)) {
193  byte[] buffer = new byte[stream.available()];
194  stream.read(buffer);
195 
196  JLnkParser lnkParser = new JLnkParser(fs.createDocumentInputStream(jmpListFileName), fileSize);
197  JLNK lnk = lnkParser.parse();
198  lnkFileName = lnk.getBestName() + ".lnk";
199  File targetFile = new File(moduleOutPath + File.separator + entry.getName() + "-" + lnkFileName);
200  String relativePath = Case.getCurrentCase().getModuleOutputDirectoryRelativePath();
201  String derivedFileName = Case.getCurrentCase().getModuleOutputDirectoryRelativePath() + File.separator + derivedPath + File.separator + entry.getName() + "-" + lnkFileName;
202  OutputStream outStream = new FileOutputStream(targetFile);
203  outStream.write(buffer);
204  outStream.close();
205  derivedFile = fileManager.addDerivedFile(lnkFileName, derivedFileName,
206  fileSize,
207  0,
208  0,
209  0,
210  0, // TBD
211  true,
212  jumpListAbsFile,
213  "",
214  moduleName,
215  VERSION_NUMBER,
216  "",
217  TskData.EncodingType.NONE);
218  derivedFiles.add(derivedFile);
219 
220  } catch (IOException | JLnkParserException ex) {
221  logger.log(Level.WARNING, String.format("No such document, or the Entry represented by documentName is not a DocumentEntry link file is %s", jumpListFile), ex); //NON-NLS
222  } catch (TskCoreException ex) {
223  logger.log(Level.WARNING, String.format("Error trying to add dervived file %s", lnkFileName), ex); //NON-NLS
224  } catch (IndexOutOfBoundsException ex) {
225  // There is some type of corruption within the file that cannot be handled, ignoring it and moving on to next file
226  // in the jumplist.
227  logger.log(Level.WARNING, String.format("Error parsing the the jumplist file %s", jumpListFile), ex); //NON-NLS
228  }
229  }
230  } else {
231  // currently, either an Entry is a DirectoryEntry or a DocumentEntry,
232  // but in the future, there may be other entry subinterfaces.
233  // The internal data structure certainly allows for a lot more entry types.
234  continue;
235  }
236  }
237  } catch (NotOLE2FileException | EmptyFileException ex1) {
238  logger.log(Level.WARNING, String.format("Error file not a valid OLE2 Document $s", jumpListFile)); //NON-NLS
239  } catch (IOException ex) {
240  logger.log(Level.WARNING, String.format("Error lnk parsing the file to get recent files $s", jumpListFile), ex); //NON-NLS
241  }
242 
243  return derivedFiles;
244 
245  }
246 
247 }
248 

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.