Autopsy  4.20.0
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 
60 final class ExtractJumpLists extends Extract {
61 
62  private static final Logger logger = Logger.getLogger(ExtractJumpLists.class.getName());
63  private static final String RA_DIR_NAME = "RecentActivity"; //NON-NLS
64  private static final String AUTOMATIC_DESTINATIONS_FILE_DIRECTORY = "%/AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/";
65  private static final String JUMPLIST_DIR_NAME = "jumplists"; //NON-NLS
66  private static final String VERSION_NUMBER = "1.0.0"; //NON-NLS
67  private String moduleName;
68  private FileManager fileManager;
69  private final IngestServices services = IngestServices.getInstance();
70  private final IngestJobContext context;
71 
72  @Messages({
73  "Jumplist_module_name=Windows Jumplist Analyzer",
74  "Jumplist_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis."
75  })
76  ExtractJumpLists(IngestJobContext context) {
77  super(Bundle.Jumplist_module_name(), context);
78  this.context = context;
79  }
80 
81  @Override
82  void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
83  moduleName = Bundle.Jumplist_module_name();
84  fileManager = currentCase.getServices().getFileManager();
85  long ingestJobId = context.getJobId();
86 
87  String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), JUMPLIST_DIR_NAME, ingestJobId);
88  List<AbstractFile> jumpListFiles = extractJumplistFiles(dataSource, ingestJobId, baseRaTempPath);
89  if (jumpListFiles.isEmpty()) {
90  return;
91  }
92 
93  if (context.dataSourceIngestIsCancelled()) {
94  return;
95  }
96 
97  List<AbstractFile> derivedFiles = new ArrayList<>();
98  String derivedPath = null;
99  String baseRaModPath = RAImageIngestModule.getRAOutputPath(Case.getCurrentCase(), JUMPLIST_DIR_NAME, ingestJobId);
100  for (AbstractFile jumplistFile : jumpListFiles) {
101  if (!jumplistFile.getName().toLowerCase().contains("-slack") && !jumplistFile.getName().equals("..")
102  && !jumplistFile.getName().equals(".") && jumplistFile.getSize() > 0) {
103  String jlFile = Paths.get(baseRaTempPath, jumplistFile.getName() + "_" + jumplistFile.getId()).toString();
104  String moduleOutPath = baseRaModPath + File.separator + jumplistFile.getName() + "_" + jumplistFile.getId();
105  derivedPath = RA_DIR_NAME + File.separator + JUMPLIST_DIR_NAME + "_" + ingestJobId + File.separator + jumplistFile.getName() + "_" + jumplistFile.getId();
106  File jlDir = new File(moduleOutPath);
107  if (jlDir.exists() == false) {
108  boolean dirMade = jlDir.mkdirs();
109  if (!dirMade) {
110  logger.log(Level.WARNING, "Error creating directory to store Jumplist LNK files %s", moduleOutPath); //NON-NLS
111  continue;
112  }
113  }
114  derivedFiles.addAll(extractLnkFiles(jlFile, moduleOutPath, jumplistFile, derivedPath));
115  }
116  }
117 
118  // notify listeners of new files and schedule for analysis
119  progressBar.progress(String.format(Bundle.Jumplist_adding_extracted_files_msg(), derivedFiles.size()));
120  derivedFiles.forEach((derived) -> {
121  services.fireModuleContentEvent(new ModuleContentEvent(derived));
122  });
123  context.addFilesToJob(derivedFiles);
124 
125  }
126 
132  private List<AbstractFile> extractJumplistFiles(Content dataSource, Long ingestJobId, String baseRaTempPath) {
133  List<AbstractFile> jumpListFiles = new ArrayList<>();;
134  List<AbstractFile> tempJumpListFiles = new ArrayList<>();;
135 
136  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
137 
138  try {
139  tempJumpListFiles = fileManager.findFiles(dataSource, "%", AUTOMATIC_DESTINATIONS_FILE_DIRECTORY); //NON-NLS
140  if (!tempJumpListFiles.isEmpty()) {
141  jumpListFiles.addAll(tempJumpListFiles);
142  }
143  } catch (TskCoreException ex) {
144  logger.log(Level.WARNING, "Unable to find jumplist files.", ex); //NON-NLS
145  return jumpListFiles; // No need to continue
146  }
147 
148  for (AbstractFile jumpListFile : jumpListFiles) {
149 
150  if (context.dataSourceIngestIsCancelled()) {
151  return jumpListFiles;
152  }
153 
154  if (!jumpListFile.getName().toLowerCase().contains("-slack") && !jumpListFile.getName().equals("..")
155  && !jumpListFile.getName().equals(".") && jumpListFile.getSize() > 0) {
156  String fileName = jumpListFile.getName() + "_" + jumpListFile.getId();
157  String jlFile = Paths.get(baseRaTempPath, fileName).toString();
158  try {
159  ContentUtils.writeToFile(jumpListFile, new File(jlFile));
160  } catch (IOException ex) {
161  logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", fileName, jlFile), ex); //NON-NLS
162  }
163  }
164  }
165 
166  return jumpListFiles;
167 
168  }
169 
170  /*
171  * Read each jumplist file and extract the lnk files to moduleoutput
172  */
173  private List<DerivedFile> extractLnkFiles(String jumpListFile, String moduleOutPath, AbstractFile jumpListAbsFile, String derivedPath) {
174 
175  List<DerivedFile> derivedFiles = new ArrayList<>();
176  DerivedFile derivedFile;
177  String lnkFileName = "";
178 
179  try (POIFSFileSystem fs = new POIFSFileSystem(new File(jumpListFile))) {
180  DirectoryEntry root = fs.getRoot();
181  for (Entry entry : root) {
182  if (entry instanceof DirectoryEntry) {
183  //If this data structure needed to recurse this is where it would do it but jumplists do not need to at this time
184  continue;
185  } else if (entry instanceof DocumentEntry) {
186  String jmpListFileName = entry.getName();
187  int fileSize = ((DocumentEntry) entry).getSize();
188 
189  if (fileSize > 0) {
190  try (DocumentInputStream stream = fs.createDocumentInputStream(jmpListFileName)) {
191  byte[] buffer = new byte[stream.available()];
192  stream.read(buffer);
193 
194  JLnkParser lnkParser = new JLnkParser(fs.createDocumentInputStream(jmpListFileName), fileSize);
195  JLNK lnk = lnkParser.parse();
196  lnkFileName = lnk.getBestName() + ".lnk";
197  File targetFile = new File(moduleOutPath + File.separator + entry.getName() + "-" + lnkFileName);
198  String relativePath = Case.getCurrentCase().getModuleOutputDirectoryRelativePath();
199  String derivedFileName = Case.getCurrentCase().getModuleOutputDirectoryRelativePath() + File.separator + derivedPath + File.separator + entry.getName() + "-" + lnkFileName;
200  OutputStream outStream = new FileOutputStream(targetFile);
201  outStream.write(buffer);
202  outStream.close();
203  derivedFile = fileManager.addDerivedFile(lnkFileName, derivedFileName,
204  fileSize,
205  0,
206  0,
207  0,
208  0, // TBD
209  true,
210  jumpListAbsFile,
211  "",
212  moduleName,
213  VERSION_NUMBER,
214  "",
215  TskData.EncodingType.NONE);
216  derivedFiles.add(derivedFile);
217 
218  } catch (IOException | JLnkParserException ex) {
219  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
220  } catch (TskCoreException ex) {
221  logger.log(Level.WARNING, String.format("Error trying to add dervived file %s", lnkFileName), ex); //NON-NLS
222  } catch (IndexOutOfBoundsException ex) {
223  // There is some type of corruption within the file that cannot be handled, ignoring it and moving on to next file
224  // in the jumplist.
225  logger.log(Level.WARNING, String.format("Error parsing the the jumplist file %s", jumpListFile), ex); //NON-NLS
226  }
227  }
228  } else {
229  // currently, either an Entry is a DirectoryEntry or a DocumentEntry,
230  // but in the future, there may be other entry subinterfaces.
231  // The internal data structure certainly allows for a lot more entry types.
232  continue;
233  }
234  }
235  } catch (NotOLE2FileException | EmptyFileException ex1) {
236  logger.log(Level.WARNING, String.format("Error file not a valid OLE2 Document $s", jumpListFile)); //NON-NLS
237  } catch (IOException ex) {
238  logger.log(Level.WARNING, String.format("Error lnk parsing the file to get recent files $s", jumpListFile), ex); //NON-NLS
239  }
240 
241  return derivedFiles;
242 
243  }
244 
245 }

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