Autopsy  4.20.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-2021 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.Optional;
39 import java.util.logging.Level;
40 import org.joda.time.Instant;
41 import org.openide.util.NbBundle.Messages;
48 import org.sleuthkit.datamodel.AbstractFile;
49 import org.sleuthkit.datamodel.Blackboard.BlackboardException;
50 import org.sleuthkit.datamodel.BlackboardArtifact;
51 import org.sleuthkit.datamodel.BlackboardAttribute;
52 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_DELETED;
53 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
54 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
55 import org.sleuthkit.datamodel.Content;
56 import org.sleuthkit.datamodel.DataSource;
57 import org.sleuthkit.datamodel.FsContent;
58 import org.sleuthkit.datamodel.OsAccount;
59 import org.sleuthkit.datamodel.SleuthkitCase;
60 import org.sleuthkit.datamodel.TskCoreException;
61 import org.sleuthkit.datamodel.TskData;
62 
70 final class ExtractRecycleBin extends Extract {
71 
72  private static final Logger logger = Logger.getLogger(ExtractRecycleBin.class.getName());
73 
74  private static final String RECYCLE_BIN_ARTIFACT_NAME = "TSK_RECYCLE_BIN"; //NON-NLS
75 
76  private static final String RECYCLE_BIN_DIR_NAME = "$RECYCLE.BIN"; //NON-NLS
77 
78  private static final int V1_FILE_NAME_OFFSET = 24;
79  private static final int V2_FILE_NAME_OFFSET = 28;
80  private final IngestJobContext context;
81 
82  @Messages({
83  "ExtractRecycleBin_module_name=Recycle Bin Analyzer"
84  })
85  ExtractRecycleBin(IngestJobContext context) {
86  super(Bundle.ExtractRecycleBin_module_name(), context);
87  this.context = context;
88  }
89 
90  @Override
91  void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
92  // At this time it was decided that we would not include TSK_RECYCLE_BIN
93  // in the default list of BlackboardArtifact types.
94  try {
95  createRecycleBinArtifactType();
96  } catch (TskCoreException ex) {
97  logger.log(Level.WARNING, String.format("%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
98  }
99 
100  BlackboardArtifact.Type recycleBinArtifactType;
101 
102  try {
103  recycleBinArtifactType = tskCase.getBlackboard().getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
104  } catch (TskCoreException ex) {
105  logger.log(Level.WARNING, String.format("Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); // NON-NLS
106  // If this doesn't work bail.
107  return;
108  }
109 
110  // map SIDs to user names so that we can include that in the artifact
111  Map<String, String> userNameMap;
112  try {
113  userNameMap = makeUserNameMap(dataSource);
114  } catch (TskCoreException ex) {
115  logger.log(Level.WARNING, "Unable to create OS Account user name map", ex);
116  // This is not the end of the world we will just continue without
117  // user names
118  userNameMap = new HashMap<>();
119  }
120 
121  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
122 
123  // Collect all of the $R files so that we can later easily map them to corresponding $I file
124  Map<String, List<AbstractFile>> rFileMap;
125  try {
126  rFileMap = makeRFileMap(dataSource);
127  } catch (TskCoreException ex) {
128  logger.log(Level.WARNING, String.format("Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
129  return; // No $R files, no need to continue;
130  }
131 
132  // Get the $I files
133  List<AbstractFile> iFiles;
134  try {
135  iFiles = fileManager.findFiles(dataSource, "$I%", RECYCLE_BIN_DIR_NAME); //NON-NLS
136  } catch (TskCoreException ex) {
137  logger.log(Level.WARNING, "Unable to find recycle bin I files.", ex); //NON-NLS
138  return; // No need to continue
139  }
140 
141  String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin", context.getJobId()); //NON-NLS
142 
143  // cycle through the $I files and process each.
144  for (AbstractFile iFile : iFiles) {
145 
146  if (context.dataSourceIngestIsCancelled()) {
147  return;
148  }
149 
150  processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
151  }
152 
153  (new File(tempRARecycleBinPath)).delete();
154  }
155 
167  private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
168  String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
169  try {
170  try {
171  ContentUtils.writeToFile(iFile, new File(tempFilePath));
172  } catch (IOException ex) {
173  logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex); //NON-NLS
174  // if we cannot make a copy of the $I file for later processing
175  // move onto the next file
176  return;
177  }
178 
179  // get the original name, dates, etc. from the $I file
180  RecycledFileMetaData metaData;
181  try {
182  metaData = parseIFile(tempFilePath);
183  } catch (IOException ex) {
184  logger.log(Level.WARNING, String.format("Unable to parse iFile %s", iFile.getParentPath() + iFile.getName()), ex); //NON-NLS
185  // Unable to parse the $I file move onto the next file
186  return;
187  }
188 
189  // each user has its own Recyle Bin folder. Figure out the user name based on its name .
190  String userID = getUserIDFromPath(iFile.getParentPath());
191  String userName = "";
192  if (!userID.isEmpty()) {
193  userName = userNameMap.get(userID);
194  } else {
195  // If the iFile doesn't have a user ID in its parent
196  // directory structure then it is not from the recyle bin
197  return;
198  }
199 
200  // get the corresponding $R file, which is in the same folder and has the file content
201  String rFileName = iFile.getName().replace("$I", "$R"); //NON-NLS
202  List<AbstractFile> rFiles = rFileMap.get(rFileName);
203  if (rFiles == null) {
204  return;
205  }
206  SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
207  for (AbstractFile rFile : rFiles) {
208  if (context.dataSourceIngestIsCancelled()) {
209  return;
210  }
211 
212  if (iFile.getParentPath().equals(rFile.getParentPath())
213  && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
214  try {
215  postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
216 
217  // If we are processing a disk image, we will also make a deleted file entry so that the user
218  // sees the deleted file in its original folder. We re-use the metadata address so that the user
219  // can see the content.
220  if (rFile instanceof FsContent) {
221  // if the user deleted a folder, then we need to recusively go into it. Note the contents of the $R folder
222  // do not have corresponding $I files anymore. Only the $R folder does.
223  if (rFile.isDir()) {
224  AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
225  popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
226 
227  } else {
228  AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
229  addFileSystemFile(skCase, (FsContent) rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
230  }
231  }
232  } catch (TskCoreException ex) {
233  logger.log(Level.WARNING, String.format("Unable to add attributes to artifact %s", rFile.getName()), ex); //NON-NLS
234  }
235  }
236  }
237  } finally {
238  (new File(tempFilePath)).delete();
239  }
240  }
241 
256  private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath, long deletedTimeStamp) throws TskCoreException {
257  if (recycledChildren == null) {
258  return;
259  }
260 
261  for (Content child : recycledChildren) {
262  if (child instanceof FsContent) {
263  FsContent fsContent = (FsContent) child;
264  if (fsContent.isFile()) {
265  addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
266  } else if (fsContent.isDir()) {
267  String newPath = parentPath + "\\" + fsContent.getName();
268  AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
269  popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
270  }
271  }
272  }
273  }
274 
309  private RecycledFileMetaData parseIFile(String iFilePath) throws IOException {
310  try {
311  byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
312 
313  ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
314  byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
315 
316  long version = byteBuffer.getLong();
317  long fileSize = byteBuffer.getLong();
318  long timestamp = byteBuffer.getLong();
319 
320  // Convert from windows FILETIME to Unix Epoch seconds
321  timestamp = Util.filetimeToMillis(timestamp) / 1000;
322 
323  byte[] stringBytes;
324 
325  if (version == 1) {
326  stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
327  } else {
328  int fileNameLength = byteBuffer.getInt() * 2; //Twice the bytes for unicode
329  stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
330  }
331 
332  String fileName = new String(stringBytes, "UTF-16LE"); //NON-NLS
333 
334  return new RecycledFileMetaData(fileSize, timestamp, fileName);
335  } catch (IOException | BufferUnderflowException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
336  throw new IOException("Error parsing $I File, file is corrupt or not a valid I$ file", ex);
337  }
338  }
339 
349  private Map<String, String> makeUserNameMap(Content dataSource) throws TskCoreException {
350  Map<String, String> userNameMap = new HashMap<>();
351 
352  for (OsAccount account : tskCase.getOsAccountManager().getOsAccounts(((DataSource) dataSource).getHost())) {
353  Optional<String> userName = account.getLoginName();
354  userNameMap.put(account.getName(), userName.isPresent() ? userName.get() : "");
355  }
356  return userNameMap;
357  }
358 
369  private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource) throws TskCoreException {
370  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
371  List<AbstractFile> rFiles = fileManager.findFiles(dataSource, "$R%");
372  Map<String, List<AbstractFile>> fileMap = new HashMap<>();
373 
374  for (AbstractFile rFile : rFiles) {
375  String fileName = rFile.getName();
376  List<AbstractFile> fileList = fileMap.get(fileName);
377 
378  if (fileList == null) {
379  fileList = new ArrayList<>();
380  fileMap.put(fileName, fileList);
381  }
382 
383  fileList.add(rFile);
384  }
385 
386  return fileMap;
387  }
388 
397  private String getUserIDFromPath(String iFileParentPath) {
398  int index = iFileParentPath.indexOf('-') - 1;
399  if (index >= 0) {
400  return (iFileParentPath.substring(index)).replace("/", "");
401  } else {
402  return "";
403  }
404  }
405 
416  private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
417  return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
418  }
419 
420  @Messages({
421  "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin"
422  })
428  private void createRecycleBinArtifactType() throws TskCoreException {
429  try {
430  tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name()); //NON-NLS
431  } catch (BlackboardException ex) {
432  throw new TskCoreException(String.format("An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
433  }
434 
435  }
436 
450  private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName, long dateTime) throws TskCoreException {
451  List<BlackboardAttribute> attributes = new ArrayList<>();
452  attributes.add(new BlackboardAttribute(TSK_PATH, getDisplayName(), fileName));
453  attributes.add(new BlackboardAttribute(TSK_DATETIME_DELETED, getDisplayName(), dateTime));
454  attributes.add(new BlackboardAttribute(TSK_USER_NAME, getDisplayName(), userName == null || userName.isEmpty() ? "" : userName));
455  return createArtifactWithAttributes(type, rFile, attributes);
456  }
457 
470  private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path) throws TskCoreException {
471 
472  String parentPath = getParentPath(path);
473  String folderName = getFileName(path);
474 
475  List<AbstractFile> files = null;
476  if (parentPath != null) {
477  if (!parentPath.equals("/")) {
478  parentPath = parentPath + "/";
479  }
480 
481  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='%s' AND name='%s'",
482  dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) : ""));
483  } else {
484  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
485  }
486 
487  if (files == null || files.isEmpty()) {
488  AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
489  return skCase.addVirtualDirectory(parent.getId(), folderName);
490  } else {
491  return files.get(0);
492  }
493  }
494 
507  private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName, long deletedTime) throws TskCoreException {
508  skCase.addFileSystemFile(
509  recycleBinFile.getDataSourceObjectId(),
510  recycleBinFile.getFileSystemId(),
511  fileName,
512  recycleBinFile.getMetaAddr(),
513  (int) recycleBinFile.getMetaSeq(),
514  recycleBinFile.getAttrType(),
515  recycleBinFile.getAttributeId(),
516  TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
517  (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
518  recycleBinFile.getSize(),
519  recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
520  true, parentDir);
521  }
522 
531  String normalizeFilePath(String pathString) {
532  if (pathString == null || pathString.isEmpty()) {
533  return null;
534  }
535 
536  Path path = Paths.get(pathString);
537  int nameCount = path.getNameCount();
538  if (nameCount > 0) {
539  String rootless = "/" + path.subpath(0, nameCount);
540  return rootless.replace("\\", "/");
541  } else {
542  return "/";
543  }
544  }
545 
555  String getFileName(String filePath) {
556  Path fileNamePath = Paths.get(filePath).getFileName();
557  if (fileNamePath != null) {
558  return fileNamePath.toString();
559  }
560  return filePath;
561  }
562 
570  String getParentPath(String path) {
571  Path parentPath = Paths.get(path).getParent();
572  if (parentPath != null) {
573  return normalizeFilePath(parentPath.toString());
574  }
575  return null;
576  }
577 
581  final class RecycledFileMetaData {
582 
583  private final long fileSize;
584  private final long deletedTimeStamp;
585  private final String fileName;
586 
594  RecycledFileMetaData(Long fileSize, long deletedTimeStamp, String fileName) {
595  this.fileSize = fileSize;
596  this.deletedTimeStamp = deletedTimeStamp;
597  this.fileName = fileName;
598  }
599 
605  long getFileSize() {
606  return fileSize;
607  }
608 
614  long getDeletedTimeStamp() {
615  return deletedTimeStamp;
616  }
617 
624  String getFullWindowsPath() {
625  return fileName.trim();
626  }
627  }
628 }

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.