Autopsy  4.13.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.FileNotFoundException;
27 import java.io.IOException;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
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 int V1_FILE_NAME_OFFSET = 24;
76  private static final int V2_FILE_NAME_OFFSET = 28;
77 
78  @Messages({
79  "ExtractRecycleBin_module_name=Recycle Bin"
80  })
81  ExtractRecycleBin() {
82  this.moduleName = Bundle.ExtractRecycleBin_module_name();
83  }
84 
85  @Override
86  void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
87  // At this time it was decided that we would not include TSK_RECYCLE_BIN
88  // in the default list of BlackboardArtifact types.
89  try {
90  createRecycleBinArtifactType();
91  } catch (TskCoreException ex) {
92  logger.log(Level.WARNING, String.format("%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
93  }
94 
95  BlackboardArtifact.Type recycleBinArtifactType;
96 
97  try {
98  recycleBinArtifactType = tskCase.getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
99  } catch (TskCoreException ex) {
100  logger.log(Level.WARNING, String.format("Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); // NON-NLS
101  // If this doesn't work bail.
102  return;
103  }
104 
105  // map SIDs to user names so that we can include that in the artifact
106  Map<String, String> userNameMap;
107  try {
108  userNameMap = makeUserNameMap(dataSource);
109  } catch (TskCoreException ex) {
110  logger.log(Level.WARNING, "Unable to create OS Account user name map", ex);
111  // This is not the end of the world we will just continue without
112  // user names
113  userNameMap = new HashMap<>();
114  }
115 
116  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
117 
118  // Collect all of the $R files so that we can later easily map them to corresponding $I file
119  Map<String, List<AbstractFile>> rFileMap;
120  try {
121  rFileMap = makeRFileMap(dataSource);
122  } catch (TskCoreException ex) {
123  logger.log(Level.WARNING, String.format("Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
124  return; // No $R files, no need to continue;
125  }
126 
127  // Get the $I files
128  List<AbstractFile> iFiles;
129  try {
130  iFiles = fileManager.findFiles(dataSource, "$I%"); //NON-NLS
131  } catch (TskCoreException ex) {
132  logger.log(Level.WARNING, "Unable to find recycle bin I files.", ex); //NON-NLS
133  return; // No need to continue
134  }
135 
136  String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin"); //NON-NLS
137 
138  // cycle through the $I files and process each.
139  for (AbstractFile iFile : iFiles) {
140 
141  if (context.dataSourceIngestIsCancelled()) {
142  return;
143  }
144 
145  processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
146  }
147 
148  (new File(tempRARecycleBinPath)).delete();
149  }
150 
160  private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
161  String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
162  try {
163  try {
164  ContentUtils.writeToFile(iFile, new File(tempFilePath));
165  } catch (IOException ex) {
166  logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex); //NON-NLS
167  // if we cannot make a copy of the $I file for later processing
168  // move onto the next file
169  return;
170  }
171 
172  // get the original name, dates, etc. from the $I file
173  RecycledFileMetaData metaData;
174  try {
175  metaData = parseIFile(tempFilePath);
176  } catch (IOException ex) {
177  logger.log(Level.WARNING, String.format("Unable to parse iFile %s", iFile.getName()), ex); //NON-NLS
178  // Unable to parse the $I file move onto the next file
179  return;
180  }
181 
182  // each user has its own Recyle Bin folder. Figure out the user name based on its name .
183  String userID = getUserIDFromPath(iFile.getParentPath());
184  String userName = "";
185  if (!userID.isEmpty()) {
186  userName = userNameMap.get(userID);
187  } else {
188  // If the iFile doesn't have a user ID in its parent
189  // directory structure then it is not from the recyle bin
190  return;
191  }
192 
193  // get the corresponding $R file, which is in the same folder and has the file content
194  String rFileName = iFile.getName().replace("$I", "$R"); //NON-NLS
195  List<AbstractFile> rFiles = rFileMap.get(rFileName);
196  if (rFiles == null) {
197  return;
198  }
199  SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
200  for (AbstractFile rFile : rFiles) {
201  if (context.dataSourceIngestIsCancelled()) {
202  return;
203  }
204 
205  if (iFile.getParentPath().equals(rFile.getParentPath())
206  && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
207  try {
208  postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
209 
210  // If we are processing a disk image, we will also make a deleted file entry so that the user
211  // sees the deleted file in its original folder. We re-use the metadata address so that the user
212  // can see the content.
213  if (rFile instanceof FsContent) {
214  // if the user deleted a folder, then we need to recusively go into it. Note the contents of the $R folder
215  // do not have corresponding $I files anymore. Only the $R folder does.
216  if (rFile.isDir()) {
217  AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
218  popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
219 
220  } else {
221  AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
222  addFileSystemFile(skCase, (FsContent)rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
223  }
224  }
225  } catch (TskCoreException ex) {
226  logger.log(Level.WARNING, String.format("Unable to add attributes to artifact %s", rFile.getName()), ex); //NON-NLS
227  }
228  }
229  }
230  } finally {
231  (new File(tempFilePath)).delete();
232  }
233  }
234 
249  private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath, long deletedTimeStamp) throws TskCoreException {
250  if (recycledChildren == null) {
251  return;
252  }
253 
254  for (Content child : recycledChildren) {
255  if (child instanceof FsContent) {
256  FsContent fsContent = (FsContent) child;
257  if (fsContent.isFile()) {
258  addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
259  } else if (fsContent.isDir()) {
260  String newPath = parentPath + "\\" + fsContent.getName();
261  AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
262  popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
263  }
264  }
265  }
266  }
267 
298  private RecycledFileMetaData parseIFile(String iFilePath) throws FileNotFoundException, IOException {
299  byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
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  }
324 
334  private Map<String, String> makeUserNameMap(Content dataSource) throws TskCoreException {
335  Map<String, String> userNameMap = new HashMap<>();
336 
337  List<BlackboardArtifact> accounts = blackboard.getArtifacts(TSK_OS_ACCOUNT.getTypeID(), dataSource.getId());
338 
339  for (BlackboardArtifact account : accounts) {
340  BlackboardAttribute nameAttribute = getAttributeForArtifact(account, TSK_USER_NAME);
341  BlackboardAttribute idAttribute = getAttributeForArtifact(account, TSK_USER_ID);
342 
343  String userName = nameAttribute != null ? nameAttribute.getDisplayString() : "";
344  String userID = idAttribute != null ? idAttribute.getDisplayString() : "";
345 
346  if (!userID.isEmpty()) {
347  userNameMap.put(userID, userName);
348  }
349  }
350 
351  return userNameMap;
352  }
353 
364  private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource) throws TskCoreException {
365  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
366  List<AbstractFile> rFiles = fileManager.findFiles(dataSource, "$R%");
367  Map<String, List<AbstractFile>> fileMap = new HashMap<>();
368 
369  for (AbstractFile rFile : rFiles) {
370  String fileName = rFile.getName();
371  List<AbstractFile> fileList = fileMap.get(fileName);
372 
373  if (fileList == null) {
374  fileList = new ArrayList<>();
375  fileMap.put(fileName, fileList);
376  }
377 
378  fileList.add(rFile);
379  }
380 
381  return fileMap;
382  }
383 
392  private String getUserIDFromPath(String iFileParentPath) {
393  int index = iFileParentPath.indexOf('-') - 1;
394  if (index >= 0) {
395  return (iFileParentPath.substring(index)).replace("/", "");
396  } else {
397  return "";
398  }
399  }
400 
411  private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
412  return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
413  }
414 
420  private void createRecycleBinArtifactType() throws TskCoreException {
421  try {
422  tskCase.addBlackboardArtifactType(RECYCLE_BIN_ARTIFACT_NAME, "Recycle Bin"); //NON-NLS
423  } catch (TskDataException ex) {
424  logger.log(Level.INFO, String.format("%s may have already been defined for this case", RECYCLE_BIN_ARTIFACT_NAME));
425  }
426 
427  }
428 
442  private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName, long dateTime) throws TskCoreException {
443  BlackboardArtifact bba = rFile.newArtifact(type.getTypeID());
444  bba.addAttribute(new BlackboardAttribute(TSK_PATH, getName(), fileName));
445  bba.addAttribute(new BlackboardAttribute(TSK_DATETIME_DELETED, getName(), dateTime));
446  bba.addAttribute(new BlackboardAttribute(TSK_USER_NAME, getName(), userName == null || userName.isEmpty() ? "" : userName));
447  return bba;
448  }
449 
462  private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path) throws TskCoreException {
463 
464  String parentPath = getParentPath(path);
465  String folderName = getFileName(path);
466 
467  List<AbstractFile> files = null;
468  if (parentPath != null) {
469  if (!parentPath.equals("/")) {
470  parentPath = parentPath + "/";
471  }
472 
473  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='%s' AND name='%s'",
474  dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) : ""));
475  } else {
476  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
477  }
478 
479  if (files == null || files.isEmpty()) {
480  AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
481  return skCase.addVirtualDirectory(parent.getId(), folderName);
482  } else {
483  return files.get(0);
484  }
485  }
486 
499  private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName, long deletedTime) throws TskCoreException {
500  skCase.addFileSystemFile(
501  recycleBinFile.getDataSourceObjectId(),
502  recycleBinFile.getFileSystemId(),
503  fileName,
504  recycleBinFile.getMetaAddr(),
505  (int) recycleBinFile.getMetaSeq(),
506  recycleBinFile.getAttrType(),
507  recycleBinFile.getAttributeId(),
508  TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
509  (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
510  recycleBinFile.getSize(),
511  recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
512  true, parentDir);
513  }
514 
523  String normalizeFilePath(String pathString) {
524  if (pathString == null || pathString.isEmpty()) {
525  return null;
526  }
527 
528  Path path = Paths.get(pathString);
529  int nameCount = path.getNameCount();
530  if(nameCount > 0) {
531  String rootless = "/" + path.subpath(0, nameCount);
532  return rootless.replace("\\", "/");
533  } else {
534  return "/";
535  }
536  }
537 
547  String getFileName(String filePath) {
548  Path fileNamePath = Paths.get(filePath).getFileName();
549  if (fileNamePath != null) {
550  return fileNamePath.toString();
551  }
552  return filePath;
553  }
554 
562  String getParentPath(String path) {
563  Path parentPath = Paths.get(path).getParent();
564  if (parentPath != null) {
565  return normalizeFilePath(parentPath.toString());
566  }
567  return null;
568  }
569 
573  final class RecycledFileMetaData {
574 
575  private final long fileSize;
576  private final long deletedTimeStamp;
577  private final String fileName;
578 
586  RecycledFileMetaData(Long fileSize, long deletedTimeStamp, String fileName) {
587  this.fileSize = fileSize;
588  this.deletedTimeStamp = deletedTimeStamp;
589  this.fileName = fileName;
590  }
591 
597  long getFileSize() {
598  return fileSize;
599  }
600 
606  long getDeletedTimeStamp() {
607  return deletedTimeStamp;
608  }
609 
616  String getFullWindowsPath() {
617  return fileName.trim();
618  }
619  }
620 }

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.