23 package org.sleuthkit.autopsy.recentactivity;
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;
38 import java.util.logging.Level;
39 import org.joda.time.Instant;
40 import org.openide.util.NbBundle.Messages;
48 import org.
sleuthkit.datamodel.Blackboard.BlackboardException;
50 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT;
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_ID;
55 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
70 final class ExtractRecycleBin
extends Extract {
72 private static final Logger logger = Logger.getLogger(ExtractRecycleBin.class.getName());
74 private static final String RECYCLE_BIN_ARTIFACT_NAME =
"TSK_RECYCLE_BIN";
76 private static final String RECYCLE_BIN_DIR_NAME =
"$RECYCLE.BIN";
78 private static final int V1_FILE_NAME_OFFSET = 24;
79 private static final int V2_FILE_NAME_OFFSET = 28;
82 "ExtractRecycleBin_module_name=Recycle Bin"
85 this.moduleName = Bundle.ExtractRecycleBin_module_name();
89 void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
93 createRecycleBinArtifactType();
94 }
catch (TskCoreException ex) {
95 logger.log(Level.WARNING, String.format(
"%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
98 BlackboardArtifact.Type recycleBinArtifactType;
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);
109 Map<String, String> userNameMap;
111 userNameMap = makeUserNameMap(dataSource);
112 }
catch (TskCoreException ex) {
113 logger.log(Level.WARNING,
"Unable to create OS Account user name map", ex);
116 userNameMap =
new HashMap<>();
119 FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
122 Map<String, List<AbstractFile>> rFileMap;
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);
131 List<AbstractFile> iFiles;
133 iFiles = fileManager.findFiles(dataSource,
"$I%", RECYCLE_BIN_DIR_NAME);
134 }
catch (TskCoreException ex) {
135 logger.log(Level.WARNING,
"Unable to find recycle bin I files.", ex);
139 String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(),
"recyclebin");
142 for (AbstractFile iFile : iFiles) {
144 if (context.dataSourceIngestIsCancelled()) {
148 processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
151 (
new File(tempRARecycleBinPath)).
delete();
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();
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);
177 RecycledFileMetaData metaData;
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);
187 String userID = getUserIDFromPath(iFile.getParentPath());
188 String userName =
"";
189 if (!userID.isEmpty()) {
190 userName = userNameMap.get(userID);
198 String rFileName = iFile.getName().replace(
"$I",
"$R");
199 List<AbstractFile> rFiles = rFileMap.get(rFileName);
200 if (rFiles == null) {
203 SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
204 for (AbstractFile rFile : rFiles) {
205 if (context.dataSourceIngestIsCancelled()) {
209 if (iFile.getParentPath().equals(rFile.getParentPath())
210 && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
212 postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
217 if (rFile instanceof FsContent) {
221 AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
222 popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
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());
229 }
catch (TskCoreException ex) {
230 logger.log(Level.WARNING, String.format(
"Unable to add attributes to artifact %s", rFile.getName()), ex);
235 (
new File(tempFilePath)).
delete();
253 private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath,
long deletedTimeStamp)
throws TskCoreException {
254 if (recycledChildren == null) {
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);
297 private RecycledFileMetaData parseIFile(String iFilePath)
throws IOException {
299 byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
302 ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
303 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
305 long version = byteBuffer.getLong();
306 long fileSize = byteBuffer.getLong();
307 long timestamp = byteBuffer.getLong();
310 timestamp = Util.filetimeToMillis(timestamp) / 1000;
315 stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
317 int fileNameLength = byteBuffer.getInt() * 2;
318 stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
321 String fileName =
new String(stringBytes,
"UTF-16LE");
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);
338 private Map<String, String> makeUserNameMap(Content dataSource)
throws TskCoreException {
339 Map<String, String> userNameMap =
new HashMap<>();
341 List<BlackboardArtifact> accounts = blackboard.getArtifacts(TSK_OS_ACCOUNT.getTypeID(), dataSource.getId());
343 for (BlackboardArtifact account : accounts) {
344 BlackboardAttribute nameAttribute = getAttributeForArtifact(account, TSK_USER_NAME);
345 BlackboardAttribute idAttribute = getAttributeForArtifact(account, TSK_USER_ID);
347 String userName = nameAttribute != null ? nameAttribute.getDisplayString() :
"";
348 String userID = idAttribute != null ? idAttribute.getDisplayString() :
"";
350 if (!userID.isEmpty()) {
351 userNameMap.put(userID, userName);
368 private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource)
throws TskCoreException {
369 FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
370 List<AbstractFile> rFiles = fileManager.findFiles(dataSource,
"$R%");
371 Map<String, List<AbstractFile>> fileMap =
new HashMap<>();
373 for (AbstractFile rFile : rFiles) {
374 String fileName = rFile.getName();
375 List<AbstractFile> fileList = fileMap.get(fileName);
377 if (fileList == null) {
378 fileList =
new ArrayList<>();
379 fileMap.put(fileName, fileList);
396 private String getUserIDFromPath(String iFileParentPath) {
397 int index = iFileParentPath.indexOf(
'-') - 1;
399 return (iFileParentPath.substring(index)).replace(
"/",
"");
415 private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
416 return artifact.getAttribute(
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
420 "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin"
427 private void createRecycleBinArtifactType() throws TskCoreException {
429 tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name());
430 }
catch (BlackboardException ex) {
431 throw new TskCoreException(String.format(
"An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
449 private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName,
long dateTime)
throws TskCoreException {
450 BlackboardArtifact bba = rFile.newArtifact(type.getTypeID());
451 bba.addAttribute(
new BlackboardAttribute(TSK_PATH, getName(), fileName));
452 bba.addAttribute(
new BlackboardAttribute(TSK_DATETIME_DELETED, getName(), dateTime));
453 bba.addAttribute(
new BlackboardAttribute(TSK_USER_NAME, getName(), userName == null || userName.isEmpty() ?
"" : userName));
469 private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path)
throws TskCoreException {
471 String parentPath = getParentPath(path);
472 String folderName = getFileName(path);
474 List<AbstractFile> files = null;
475 if (parentPath != null) {
476 if (!parentPath.equals(
"/")) {
477 parentPath = parentPath +
"/";
480 files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='%s' AND name='%s'",
481 dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) :
""));
483 files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
486 if (files == null || files.isEmpty()) {
487 AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
488 return skCase.addVirtualDirectory(parent.getId(), folderName);
506 private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName,
long deletedTime)
throws TskCoreException {
507 skCase.addFileSystemFile(
508 recycleBinFile.getDataSourceObjectId(),
509 recycleBinFile.getFileSystemId(),
511 recycleBinFile.getMetaAddr(),
512 (int) recycleBinFile.getMetaSeq(),
513 recycleBinFile.getAttrType(),
514 recycleBinFile.getAttributeId(),
515 TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
516 (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
517 recycleBinFile.getSize(),
518 recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
530 String normalizeFilePath(String pathString) {
531 if (pathString == null || pathString.isEmpty()) {
535 Path path = Paths.get(pathString);
536 int nameCount = path.getNameCount();
538 String rootless =
"/" + path.subpath(0, nameCount);
539 return rootless.replace(
"\\",
"/");
554 String getFileName(String filePath) {
555 Path fileNamePath = Paths.get(filePath).getFileName();
556 if (fileNamePath != null) {
557 return fileNamePath.toString();
569 String getParentPath(String path) {
570 Path parentPath = Paths.get(path).getParent();
571 if (parentPath != null) {
572 return normalizeFilePath(parentPath.toString());
580 final class RecycledFileMetaData {
582 private final long fileSize;
583 private final long deletedTimeStamp;
584 private final String fileName;
593 RecycledFileMetaData(Long fileSize,
long deletedTimeStamp, String fileName) {
594 this.fileSize = fileSize;
595 this.deletedTimeStamp = deletedTimeStamp;
596 this.fileName = fileName;
613 long getDeletedTimeStamp() {
614 return deletedTimeStamp;
623 String getFullWindowsPath() {
624 return fileName.trim();