Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractRecycleBin.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2019 Basis Technology Corp.
6  *
7  * Copyright 2012 42six Solutions.
8  * Contact: aebadirad <at> 42six <dot> com
9  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 package org.sleuthkit.autopsy.recentactivity;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.nio.BufferUnderflowException;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.logging.Level;
39 import org.joda.time.Instant;
40 import org.openide.util.NbBundle.Messages;
47 import org.sleuthkit.datamodel.AbstractFile;
48 import org.sleuthkit.datamodel.BlackboardArtifact;
49 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT;
50 import org.sleuthkit.datamodel.BlackboardAttribute;
51 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_DELETED;
52 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
53 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID;
54 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
55 import org.sleuthkit.datamodel.Content;
56 import org.sleuthkit.datamodel.FsContent;
57 import org.sleuthkit.datamodel.SleuthkitCase;
58 import org.sleuthkit.datamodel.TskCoreException;
59 import org.sleuthkit.datamodel.TskData;
60 import org.sleuthkit.datamodel.TskDataException;
61 
69 final class ExtractRecycleBin extends Extract {
70 
71  private static final Logger logger = Logger.getLogger(ExtractRecycleBin.class.getName());
72 
73  private static final String RECYCLE_BIN_ARTIFACT_NAME = "TSK_RECYCLE_BIN"; //NON-NLS
74 
75  private static final String RECYCLE_BIN_DIR_NAME = "$RECYCLE.BIN"; //NON-NLS
76 
77  private static final int V1_FILE_NAME_OFFSET = 24;
78  private static final int V2_FILE_NAME_OFFSET = 28;
79 
80  @Messages({
81  "ExtractRecycleBin_module_name=Recycle Bin"
82  })
83  ExtractRecycleBin() {
84  this.moduleName = Bundle.ExtractRecycleBin_module_name();
85  }
86 
87  @Override
88  void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
89  // At this time it was decided that we would not include TSK_RECYCLE_BIN
90  // in the default list of BlackboardArtifact types.
91  try {
92  createRecycleBinArtifactType();
93  } catch (TskCoreException ex) {
94  logger.log(Level.WARNING, String.format("%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
95  }
96 
97  BlackboardArtifact.Type recycleBinArtifactType;
98 
99  try {
100  recycleBinArtifactType = tskCase.getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
101  } catch (TskCoreException ex) {
102  logger.log(Level.WARNING, String.format("Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); // NON-NLS
103  // If this doesn't work bail.
104  return;
105  }
106 
107  // map SIDs to user names so that we can include that in the artifact
108  Map<String, String> userNameMap;
109  try {
110  userNameMap = makeUserNameMap(dataSource);
111  } catch (TskCoreException ex) {
112  logger.log(Level.WARNING, "Unable to create OS Account user name map", ex);
113  // This is not the end of the world we will just continue without
114  // user names
115  userNameMap = new HashMap<>();
116  }
117 
118  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
119 
120  // Collect all of the $R files so that we can later easily map them to corresponding $I file
121  Map<String, List<AbstractFile>> rFileMap;
122  try {
123  rFileMap = makeRFileMap(dataSource);
124  } catch (TskCoreException ex) {
125  logger.log(Level.WARNING, String.format("Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
126  return; // No $R files, no need to continue;
127  }
128 
129  // Get the $I files
130  List<AbstractFile> iFiles;
131  try {
132  iFiles = fileManager.findFiles(dataSource, "$I%", RECYCLE_BIN_DIR_NAME); //NON-NLS
133  } catch (TskCoreException ex) {
134  logger.log(Level.WARNING, "Unable to find recycle bin I files.", ex); //NON-NLS
135  return; // No need to continue
136  }
137 
138  String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin"); //NON-NLS
139 
140  // cycle through the $I files and process each.
141  for (AbstractFile iFile : iFiles) {
142 
143  if (context.dataSourceIngestIsCancelled()) {
144  return;
145  }
146 
147  processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
148  }
149 
150  (new File(tempRARecycleBinPath)).delete();
151  }
152 
163  private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
164  String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
165  try {
166  try {
167  ContentUtils.writeToFile(iFile, new File(tempFilePath));
168  } catch (IOException ex) {
169  logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex); //NON-NLS
170  // if we cannot make a copy of the $I file for later processing
171  // move onto the next file
172  return;
173  }
174 
175  // get the original name, dates, etc. from the $I file
176  RecycledFileMetaData metaData;
177  try {
178  metaData = parseIFile(tempFilePath);
179  } catch (IOException ex) {
180  logger.log(Level.WARNING, String.format("Unable to parse iFile %s", iFile.getParentPath() + iFile.getName()), ex); //NON-NLS
181  // Unable to parse the $I file move onto the next file
182  return;
183  }
184 
185  // each user has its own Recyle Bin folder. Figure out the user name based on its name .
186  String userID = getUserIDFromPath(iFile.getParentPath());
187  String userName = "";
188  if (!userID.isEmpty()) {
189  userName = userNameMap.get(userID);
190  } else {
191  // If the iFile doesn't have a user ID in its parent
192  // directory structure then it is not from the recyle bin
193  return;
194  }
195 
196  // get the corresponding $R file, which is in the same folder and has the file content
197  String rFileName = iFile.getName().replace("$I", "$R"); //NON-NLS
198  List<AbstractFile> rFiles = rFileMap.get(rFileName);
199  if (rFiles == null) {
200  return;
201  }
202  SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
203  for (AbstractFile rFile : rFiles) {
204  if (context.dataSourceIngestIsCancelled()) {
205  return;
206  }
207 
208  if (iFile.getParentPath().equals(rFile.getParentPath())
209  && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
210  try {
211  postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
212 
213  // If we are processing a disk image, we will also make a deleted file entry so that the user
214  // sees the deleted file in its original folder. We re-use the metadata address so that the user
215  // can see the content.
216  if (rFile instanceof FsContent) {
217  // if the user deleted a folder, then we need to recusively go into it. Note the contents of the $R folder
218  // do not have corresponding $I files anymore. Only the $R folder does.
219  if (rFile.isDir()) {
220  AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
221  popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
222 
223  } else {
224  AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
225  addFileSystemFile(skCase, (FsContent)rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
226  }
227  }
228  } catch (TskCoreException ex) {
229  logger.log(Level.WARNING, String.format("Unable to add attributes to artifact %s", rFile.getName()), ex); //NON-NLS
230  }
231  }
232  }
233  } finally {
234  (new File(tempFilePath)).delete();
235  }
236  }
237 
252  private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath, long deletedTimeStamp) throws TskCoreException {
253  if (recycledChildren == null) {
254  return;
255  }
256 
257  for (Content child : recycledChildren) {
258  if (child instanceof FsContent) {
259  FsContent fsContent = (FsContent) child;
260  if (fsContent.isFile()) {
261  addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
262  } else if (fsContent.isDir()) {
263  String newPath = parentPath + "\\" + fsContent.getName();
264  AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
265  popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
266  }
267  }
268  }
269  }
270 
296  private RecycledFileMetaData parseIFile(String iFilePath) throws IOException {
297  try {
298  byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
299 
300 
301  ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
302  byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
303 
304  long version = byteBuffer.getLong();
305  long fileSize = byteBuffer.getLong();
306  long timestamp = byteBuffer.getLong();
307 
308  // Convert from windows FILETIME to Unix Epoch seconds
309  timestamp = Util.filetimeToMillis(timestamp) / 1000;
310 
311  byte[] stringBytes;
312 
313  if (version == 1) {
314  stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
315  } else {
316  int fileNameLength = byteBuffer.getInt() * 2; //Twice the bytes for unicode
317  stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
318  }
319 
320  String fileName = new String(stringBytes, "UTF-16LE"); //NON-NLS
321 
322  return new RecycledFileMetaData(fileSize, timestamp, fileName);
323  } catch (IOException | BufferUnderflowException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
324  throw new IOException("Error parsing $I File, file is corrupt or not a valid I$ file", ex);
325  }
326  }
327 
337  private Map<String, String> makeUserNameMap(Content dataSource) throws TskCoreException {
338  Map<String, String> userNameMap = new HashMap<>();
339 
340  List<BlackboardArtifact> accounts = blackboard.getArtifacts(TSK_OS_ACCOUNT.getTypeID(), dataSource.getId());
341 
342  for (BlackboardArtifact account : accounts) {
343  BlackboardAttribute nameAttribute = getAttributeForArtifact(account, TSK_USER_NAME);
344  BlackboardAttribute idAttribute = getAttributeForArtifact(account, TSK_USER_ID);
345 
346  String userName = nameAttribute != null ? nameAttribute.getDisplayString() : "";
347  String userID = idAttribute != null ? idAttribute.getDisplayString() : "";
348 
349  if (!userID.isEmpty()) {
350  userNameMap.put(userID, userName);
351  }
352  }
353 
354  return userNameMap;
355  }
356 
367  private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource) throws TskCoreException {
368  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
369  List<AbstractFile> rFiles = fileManager.findFiles(dataSource, "$R%");
370  Map<String, List<AbstractFile>> fileMap = new HashMap<>();
371 
372  for (AbstractFile rFile : rFiles) {
373  String fileName = rFile.getName();
374  List<AbstractFile> fileList = fileMap.get(fileName);
375 
376  if (fileList == null) {
377  fileList = new ArrayList<>();
378  fileMap.put(fileName, fileList);
379  }
380 
381  fileList.add(rFile);
382  }
383 
384  return fileMap;
385  }
386 
395  private String getUserIDFromPath(String iFileParentPath) {
396  int index = iFileParentPath.indexOf('-') - 1;
397  if (index >= 0) {
398  return (iFileParentPath.substring(index)).replace("/", "");
399  } else {
400  return "";
401  }
402  }
403 
414  private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
415  return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
416  }
417 
423  private void createRecycleBinArtifactType() throws TskCoreException {
424  try {
425  tskCase.addBlackboardArtifactType(RECYCLE_BIN_ARTIFACT_NAME, "Recycle Bin"); //NON-NLS
426  } catch (TskDataException ex) {
427  logger.log(Level.INFO, String.format("%s may have already been defined for this case", RECYCLE_BIN_ARTIFACT_NAME));
428  }
429 
430  }
431 
445  private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName, long dateTime) throws TskCoreException {
446  BlackboardArtifact bba = rFile.newArtifact(type.getTypeID());
447  bba.addAttribute(new BlackboardAttribute(TSK_PATH, getName(), fileName));
448  bba.addAttribute(new BlackboardAttribute(TSK_DATETIME_DELETED, getName(), dateTime));
449  bba.addAttribute(new BlackboardAttribute(TSK_USER_NAME, getName(), userName == null || userName.isEmpty() ? "" : userName));
450  return bba;
451  }
452 
465  private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path) throws TskCoreException {
466 
467  String parentPath = getParentPath(path);
468  String folderName = getFileName(path);
469 
470  List<AbstractFile> files = null;
471  if (parentPath != null) {
472  if (!parentPath.equals("/")) {
473  parentPath = parentPath + "/";
474  }
475 
476  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='%s' AND name='%s'",
477  dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) : ""));
478  } else {
479  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
480  }
481 
482  if (files == null || files.isEmpty()) {
483  AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
484  return skCase.addVirtualDirectory(parent.getId(), folderName);
485  } else {
486  return files.get(0);
487  }
488  }
489 
502  private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName, long deletedTime) throws TskCoreException {
503  skCase.addFileSystemFile(
504  recycleBinFile.getDataSourceObjectId(),
505  recycleBinFile.getFileSystemId(),
506  fileName,
507  recycleBinFile.getMetaAddr(),
508  (int) recycleBinFile.getMetaSeq(),
509  recycleBinFile.getAttrType(),
510  recycleBinFile.getAttributeId(),
511  TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
512  (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
513  recycleBinFile.getSize(),
514  recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
515  true, parentDir);
516  }
517 
526  String normalizeFilePath(String pathString) {
527  if (pathString == null || pathString.isEmpty()) {
528  return null;
529  }
530 
531  Path path = Paths.get(pathString);
532  int nameCount = path.getNameCount();
533  if(nameCount > 0) {
534  String rootless = "/" + path.subpath(0, nameCount);
535  return rootless.replace("\\", "/");
536  } else {
537  return "/";
538  }
539  }
540 
550  String getFileName(String filePath) {
551  Path fileNamePath = Paths.get(filePath).getFileName();
552  if (fileNamePath != null) {
553  return fileNamePath.toString();
554  }
555  return filePath;
556  }
557 
565  String getParentPath(String path) {
566  Path parentPath = Paths.get(path).getParent();
567  if (parentPath != null) {
568  return normalizeFilePath(parentPath.toString());
569  }
570  return null;
571  }
572 
576  final class RecycledFileMetaData {
577 
578  private final long fileSize;
579  private final long deletedTimeStamp;
580  private final String fileName;
581 
589  RecycledFileMetaData(Long fileSize, long deletedTimeStamp, String fileName) {
590  this.fileSize = fileSize;
591  this.deletedTimeStamp = deletedTimeStamp;
592  this.fileName = fileName;
593  }
594 
600  long getFileSize() {
601  return fileSize;
602  }
603 
609  long getDeletedTimeStamp() {
610  return deletedTimeStamp;
611  }
612 
619  String getFullWindowsPath() {
620  return fileName.trim();
621  }
622  }
623 }

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.