Autopsy 4.22.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-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 */
23package org.sleuthkit.autopsy.recentactivity;
24
25import java.io.File;
26import java.io.IOException;
27import java.nio.ByteBuffer;
28import java.nio.ByteOrder;
29import java.nio.BufferUnderflowException;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.HashMap;
36import java.util.List;
37import java.util.Map;
38import java.util.Optional;
39import java.util.logging.Level;
40import org.joda.time.Instant;
41import org.openide.util.NbBundle.Messages;
42import org.sleuthkit.autopsy.casemodule.Case;
43import org.sleuthkit.autopsy.casemodule.services.FileManager;
44import org.sleuthkit.autopsy.coreutils.Logger;
45import org.sleuthkit.autopsy.datamodel.ContentUtils;
46import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
47import org.sleuthkit.autopsy.ingest.IngestJobContext;
48import org.sleuthkit.datamodel.AbstractFile;
49import org.sleuthkit.datamodel.Blackboard.BlackboardException;
50import org.sleuthkit.datamodel.BlackboardArtifact;
51import org.sleuthkit.datamodel.BlackboardAttribute;
52import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_DELETED;
53import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
54import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
55import org.sleuthkit.datamodel.Content;
56import org.sleuthkit.datamodel.DataSource;
57import org.sleuthkit.datamodel.FsContent;
58import org.sleuthkit.datamodel.OsAccount;
59import org.sleuthkit.datamodel.SleuthkitCase;
60import org.sleuthkit.datamodel.TskCoreException;
61import org.sleuthkit.datamodel.TskData;
62
70final 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-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.