Autopsy  4.19.1
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.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 
81  @Messages({
82  "ExtractRecycleBin_module_name=Recycle Bin"
83  })
84  ExtractRecycleBin() {
85  super(Bundle.ExtractRecycleBin_module_name());
86  }
87 
88  @Override
89  void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
90  // At this time it was decided that we would not include TSK_RECYCLE_BIN
91  // in the default list of BlackboardArtifact types.
92  try {
93  createRecycleBinArtifactType();
94  } catch (TskCoreException ex) {
95  logger.log(Level.WARNING, String.format("%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
96  }
97 
98  BlackboardArtifact.Type recycleBinArtifactType;
99 
100  try {
101  recycleBinArtifactType = tskCase.getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
102  } catch (TskCoreException ex) {
103  logger.log(Level.WARNING, String.format("Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); // NON-NLS
104  // If this doesn't work bail.
105  return;
106  }
107 
108  // map SIDs to user names so that we can include that in the artifact
109  Map<String, String> userNameMap;
110  try {
111  userNameMap = makeUserNameMap(dataSource);
112  } catch (TskCoreException ex) {
113  logger.log(Level.WARNING, "Unable to create OS Account user name map", ex);
114  // This is not the end of the world we will just continue without
115  // user names
116  userNameMap = new HashMap<>();
117  }
118 
119  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
120 
121  // Collect all of the $R files so that we can later easily map them to corresponding $I file
122  Map<String, List<AbstractFile>> rFileMap;
123  try {
124  rFileMap = makeRFileMap(dataSource);
125  } catch (TskCoreException ex) {
126  logger.log(Level.WARNING, String.format("Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
127  return; // No $R files, no need to continue;
128  }
129 
130  // Get the $I files
131  List<AbstractFile> iFiles;
132  try {
133  iFiles = fileManager.findFiles(dataSource, "$I%", RECYCLE_BIN_DIR_NAME); //NON-NLS
134  } catch (TskCoreException ex) {
135  logger.log(Level.WARNING, "Unable to find recycle bin I files.", ex); //NON-NLS
136  return; // No need to continue
137  }
138 
139  String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin", context.getJobId()); //NON-NLS
140 
141  // cycle through the $I files and process each.
142  for (AbstractFile iFile : iFiles) {
143 
144  if (context.dataSourceIngestIsCancelled()) {
145  return;
146  }
147 
148  processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
149  }
150 
151  (new File(tempRARecycleBinPath)).delete();
152  }
153 
164  private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
165  String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
166  try {
167  try {
168  ContentUtils.writeToFile(iFile, new File(tempFilePath));
169  } catch (IOException ex) {
170  logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex); //NON-NLS
171  // if we cannot make a copy of the $I file for later processing
172  // move onto the next file
173  return;
174  }
175 
176  // get the original name, dates, etc. from the $I file
177  RecycledFileMetaData metaData;
178  try {
179  metaData = parseIFile(tempFilePath);
180  } catch (IOException ex) {
181  logger.log(Level.WARNING, String.format("Unable to parse iFile %s", iFile.getParentPath() + iFile.getName()), ex); //NON-NLS
182  // Unable to parse the $I file move onto the next file
183  return;
184  }
185 
186  // each user has its own Recyle Bin folder. Figure out the user name based on its name .
187  String userID = getUserIDFromPath(iFile.getParentPath());
188  String userName = "";
189  if (!userID.isEmpty()) {
190  userName = userNameMap.get(userID);
191  } else {
192  // If the iFile doesn't have a user ID in its parent
193  // directory structure then it is not from the recyle bin
194  return;
195  }
196 
197  // get the corresponding $R file, which is in the same folder and has the file content
198  String rFileName = iFile.getName().replace("$I", "$R"); //NON-NLS
199  List<AbstractFile> rFiles = rFileMap.get(rFileName);
200  if (rFiles == null) {
201  return;
202  }
203  SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
204  for (AbstractFile rFile : rFiles) {
205  if (context.dataSourceIngestIsCancelled()) {
206  return;
207  }
208 
209  if (iFile.getParentPath().equals(rFile.getParentPath())
210  && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
211  try {
212  postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
213 
214  // If we are processing a disk image, we will also make a deleted file entry so that the user
215  // sees the deleted file in its original folder. We re-use the metadata address so that the user
216  // can see the content.
217  if (rFile instanceof FsContent) {
218  // if the user deleted a folder, then we need to recusively go into it. Note the contents of the $R folder
219  // do not have corresponding $I files anymore. Only the $R folder does.
220  if (rFile.isDir()) {
221  AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
222  popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
223 
224  } else {
225  AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
226  addFileSystemFile(skCase, (FsContent)rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
227  }
228  }
229  } catch (TskCoreException ex) {
230  logger.log(Level.WARNING, String.format("Unable to add attributes to artifact %s", rFile.getName()), ex); //NON-NLS
231  }
232  }
233  }
234  } finally {
235  (new File(tempFilePath)).delete();
236  }
237  }
238 
253  private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath, long deletedTimeStamp) throws TskCoreException {
254  if (recycledChildren == null) {
255  return;
256  }
257 
258  for (Content child : recycledChildren) {
259  if (child instanceof FsContent) {
260  FsContent fsContent = (FsContent) child;
261  if (fsContent.isFile()) {
262  addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
263  } else if (fsContent.isDir()) {
264  String newPath = parentPath + "\\" + fsContent.getName();
265  AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
266  popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
267  }
268  }
269  }
270  }
271 
297  private RecycledFileMetaData parseIFile(String iFilePath) throws IOException {
298  try {
299  byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
300 
301 
302  ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
303  byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
304 
305  long version = byteBuffer.getLong();
306  long fileSize = byteBuffer.getLong();
307  long timestamp = byteBuffer.getLong();
308 
309  // Convert from windows FILETIME to Unix Epoch seconds
310  timestamp = Util.filetimeToMillis(timestamp) / 1000;
311 
312  byte[] stringBytes;
313 
314  if (version == 1) {
315  stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
316  } else {
317  int fileNameLength = byteBuffer.getInt() * 2; //Twice the bytes for unicode
318  stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
319  }
320 
321  String fileName = new String(stringBytes, "UTF-16LE"); //NON-NLS
322 
323  return new RecycledFileMetaData(fileSize, timestamp, fileName);
324  } catch (IOException | BufferUnderflowException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
325  throw new IOException("Error parsing $I File, file is corrupt or not a valid I$ file", ex);
326  }
327  }
328 
338  private Map<String, String> makeUserNameMap(Content dataSource) throws TskCoreException {
339  Map<String, String> userNameMap = new HashMap<>();
340 
341  for(OsAccount account: tskCase.getOsAccountManager().getOsAccounts(((DataSource)dataSource).getHost())) {
342  Optional<String> userName = account.getLoginName();
343  userNameMap.put(account.getName(), userName.isPresent() ? userName.get() : "");
344  }
345  return userNameMap;
346  }
347 
358  private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource) throws TskCoreException {
359  FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
360  List<AbstractFile> rFiles = fileManager.findFiles(dataSource, "$R%");
361  Map<String, List<AbstractFile>> fileMap = new HashMap<>();
362 
363  for (AbstractFile rFile : rFiles) {
364  String fileName = rFile.getName();
365  List<AbstractFile> fileList = fileMap.get(fileName);
366 
367  if (fileList == null) {
368  fileList = new ArrayList<>();
369  fileMap.put(fileName, fileList);
370  }
371 
372  fileList.add(rFile);
373  }
374 
375  return fileMap;
376  }
377 
386  private String getUserIDFromPath(String iFileParentPath) {
387  int index = iFileParentPath.indexOf('-') - 1;
388  if (index >= 0) {
389  return (iFileParentPath.substring(index)).replace("/", "");
390  } else {
391  return "";
392  }
393  }
394 
405  private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
406  return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
407  }
408 
409  @Messages({
410  "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin"
411  })
417  private void createRecycleBinArtifactType() throws TskCoreException {
418  try {
419  tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name()); //NON-NLS
420  } catch (BlackboardException ex) {
421  throw new TskCoreException(String.format("An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
422  }
423 
424  }
425 
439  private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName, long dateTime) throws TskCoreException {
440  List<BlackboardAttribute> attributes = new ArrayList<>();
441  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
442  attributes.add(new BlackboardAttribute(TSK_DATETIME_DELETED, getName(), dateTime));
443  attributes.add(new BlackboardAttribute(TSK_USER_NAME, getName(), userName == null || userName.isEmpty() ? "" : userName));
444  return createArtifactWithAttributes(type, rFile, attributes);
445  }
446 
459  private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path) throws TskCoreException {
460 
461  String parentPath = getParentPath(path);
462  String folderName = getFileName(path);
463 
464  List<AbstractFile> files = null;
465  if (parentPath != null) {
466  if (!parentPath.equals("/")) {
467  parentPath = parentPath + "/";
468  }
469 
470  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='%s' AND name='%s'",
471  dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) : ""));
472  } else {
473  files = skCase.findAllFilesWhere(String.format("fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
474  }
475 
476  if (files == null || files.isEmpty()) {
477  AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
478  return skCase.addVirtualDirectory(parent.getId(), folderName);
479  } else {
480  return files.get(0);
481  }
482  }
483 
496  private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName, long deletedTime) throws TskCoreException {
497  skCase.addFileSystemFile(
498  recycleBinFile.getDataSourceObjectId(),
499  recycleBinFile.getFileSystemId(),
500  fileName,
501  recycleBinFile.getMetaAddr(),
502  (int) recycleBinFile.getMetaSeq(),
503  recycleBinFile.getAttrType(),
504  recycleBinFile.getAttributeId(),
505  TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
506  (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
507  recycleBinFile.getSize(),
508  recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
509  true, parentDir);
510  }
511 
520  String normalizeFilePath(String pathString) {
521  if (pathString == null || pathString.isEmpty()) {
522  return null;
523  }
524 
525  Path path = Paths.get(pathString);
526  int nameCount = path.getNameCount();
527  if(nameCount > 0) {
528  String rootless = "/" + path.subpath(0, nameCount);
529  return rootless.replace("\\", "/");
530  } else {
531  return "/";
532  }
533  }
534 
544  String getFileName(String filePath) {
545  Path fileNamePath = Paths.get(filePath).getFileName();
546  if (fileNamePath != null) {
547  return fileNamePath.toString();
548  }
549  return filePath;
550  }
551 
559  String getParentPath(String path) {
560  Path parentPath = Paths.get(path).getParent();
561  if (parentPath != null) {
562  return normalizeFilePath(parentPath.toString());
563  }
564  return null;
565  }
566 
570  final class RecycledFileMetaData {
571 
572  private final long fileSize;
573  private final long deletedTimeStamp;
574  private final String fileName;
575 
583  RecycledFileMetaData(Long fileSize, long deletedTimeStamp, String fileName) {
584  this.fileSize = fileSize;
585  this.deletedTimeStamp = deletedTimeStamp;
586  this.fileName = fileName;
587  }
588 
594  long getFileSize() {
595  return fileSize;
596  }
597 
603  long getDeletedTimeStamp() {
604  return deletedTimeStamp;
605  }
606 
613  String getFullWindowsPath() {
614  return fileName.trim();
615  }
616  }
617 }

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.