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;
49 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT;
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;
69 final class ExtractRecycleBin
extends Extract {
71 private static final Logger logger = Logger.getLogger(ExtractRecycleBin.class.getName());
73 private static final String RECYCLE_BIN_ARTIFACT_NAME =
"TSK_RECYCLE_BIN";
75 private static final String RECYCLE_BIN_DIR_NAME =
"$RECYCLE.BIN";
77 private static final int V1_FILE_NAME_OFFSET = 24;
78 private static final int V2_FILE_NAME_OFFSET = 28;
81 "ExtractRecycleBin_module_name=Recycle Bin"
84 this.moduleName = Bundle.ExtractRecycleBin_module_name();
88 void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
92 createRecycleBinArtifactType();
93 }
catch (TskCoreException ex) {
94 logger.log(Level.WARNING, String.format(
"%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
97 BlackboardArtifact.Type recycleBinArtifactType;
100 recycleBinArtifactType = tskCase.getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
101 }
catch (TskCoreException ex) {
102 logger.log(Level.WARNING, String.format(
"Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
108 Map<String, String> userNameMap;
110 userNameMap = makeUserNameMap(dataSource);
111 }
catch (TskCoreException ex) {
112 logger.log(Level.WARNING,
"Unable to create OS Account user name map", ex);
115 userNameMap =
new HashMap<>();
118 FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
121 Map<String, List<AbstractFile>> rFileMap;
123 rFileMap = makeRFileMap(dataSource);
124 }
catch (TskCoreException ex) {
125 logger.log(Level.WARNING, String.format(
"Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
130 List<AbstractFile> iFiles;
132 iFiles = fileManager.findFiles(dataSource,
"$I%", RECYCLE_BIN_DIR_NAME);
133 }
catch (TskCoreException ex) {
134 logger.log(Level.WARNING,
"Unable to find recycle bin I files.", ex);
138 String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(),
"recyclebin");
141 for (AbstractFile iFile : iFiles) {
143 if (context.dataSourceIngestIsCancelled()) {
147 processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
150 (
new File(tempRARecycleBinPath)).
delete();
163 private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
164 String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
167 ContentUtils.writeToFile(iFile,
new File(tempFilePath));
168 }
catch (IOException ex) {
169 logger.log(Level.WARNING, String.format(
"Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex);
176 RecycledFileMetaData metaData;
178 metaData = parseIFile(tempFilePath);
179 }
catch (IOException ex) {
180 logger.log(Level.WARNING, String.format(
"Unable to parse iFile %s", iFile.getParentPath() + iFile.getName()), ex);
186 String userID = getUserIDFromPath(iFile.getParentPath());
187 String userName =
"";
188 if (!userID.isEmpty()) {
189 userName = userNameMap.get(userID);
197 String rFileName = iFile.getName().replace(
"$I",
"$R");
198 List<AbstractFile> rFiles = rFileMap.get(rFileName);
199 if (rFiles == null) {
202 SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
203 for (AbstractFile rFile : rFiles) {
204 if (context.dataSourceIngestIsCancelled()) {
208 if (iFile.getParentPath().equals(rFile.getParentPath())
209 && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
211 postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
216 if (rFile instanceof FsContent) {
220 AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
221 popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
224 AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
225 addFileSystemFile(skCase, (FsContent)rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
228 }
catch (TskCoreException ex) {
229 logger.log(Level.WARNING, String.format(
"Unable to add attributes to artifact %s", rFile.getName()), ex);
234 (
new File(tempFilePath)).
delete();
252 private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath,
long deletedTimeStamp)
throws TskCoreException {
253 if (recycledChildren == null) {
257 for (Content child : recycledChildren) {
258 if (child instanceof FsContent) {
259 FsContent fsContent = (FsContent) child;
260 if (fsContent.isFile()) {
261 addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
262 }
else if (fsContent.isDir()) {
263 String newPath = parentPath +
"\\" + fsContent.getName();
264 AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
265 popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
296 private RecycledFileMetaData parseIFile(String iFilePath)
throws IOException {
298 byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
301 ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
302 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
304 long version = byteBuffer.getLong();
305 long fileSize = byteBuffer.getLong();
306 long timestamp = byteBuffer.getLong();
309 timestamp = Util.filetimeToMillis(timestamp) / 1000;
314 stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
316 int fileNameLength = byteBuffer.getInt() * 2;
317 stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
320 String fileName =
new String(stringBytes,
"UTF-16LE");
322 return new RecycledFileMetaData(fileSize, timestamp, fileName);
323 }
catch (IOException | BufferUnderflowException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
324 throw new IOException(
"Error parsing $I File, file is corrupt or not a valid I$ file", ex);
337 private Map<String, String> makeUserNameMap(Content dataSource)
throws TskCoreException {
338 Map<String, String> userNameMap =
new HashMap<>();
340 List<BlackboardArtifact> accounts = blackboard.getArtifacts(TSK_OS_ACCOUNT.getTypeID(), dataSource.getId());
342 for (BlackboardArtifact account : accounts) {
343 BlackboardAttribute nameAttribute = getAttributeForArtifact(account, TSK_USER_NAME);
344 BlackboardAttribute idAttribute = getAttributeForArtifact(account, TSK_USER_ID);
346 String userName = nameAttribute != null ? nameAttribute.getDisplayString() :
"";
347 String userID = idAttribute != null ? idAttribute.getDisplayString() :
"";
349 if (!userID.isEmpty()) {
350 userNameMap.put(userID, userName);
367 private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource)
throws TskCoreException {
368 FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
369 List<AbstractFile> rFiles = fileManager.findFiles(dataSource,
"$R%");
370 Map<String, List<AbstractFile>> fileMap =
new HashMap<>();
372 for (AbstractFile rFile : rFiles) {
373 String fileName = rFile.getName();
374 List<AbstractFile> fileList = fileMap.get(fileName);
376 if (fileList == null) {
377 fileList =
new ArrayList<>();
378 fileMap.put(fileName, fileList);
395 private String getUserIDFromPath(String iFileParentPath) {
396 int index = iFileParentPath.indexOf(
'-') - 1;
398 return (iFileParentPath.substring(index)).replace(
"/",
"");
414 private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
415 return artifact.getAttribute(
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
423 private void createRecycleBinArtifactType() throws TskCoreException {
425 tskCase.addBlackboardArtifactType(RECYCLE_BIN_ARTIFACT_NAME,
"Recycle Bin");
426 }
catch (TskDataException ex) {
427 logger.log(Level.INFO, String.format(
"%s may have already been defined for this case", RECYCLE_BIN_ARTIFACT_NAME));
445 private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName,
long dateTime)
throws TskCoreException {
446 BlackboardArtifact bba = rFile.newArtifact(type.getTypeID());
447 bba.addAttribute(
new BlackboardAttribute(TSK_PATH, getName(), fileName));
448 bba.addAttribute(
new BlackboardAttribute(TSK_DATETIME_DELETED, getName(), dateTime));
449 bba.addAttribute(
new BlackboardAttribute(TSK_USER_NAME, getName(), userName == null || userName.isEmpty() ?
"" : userName));
465 private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path)
throws TskCoreException {
467 String parentPath = getParentPath(path);
468 String folderName = getFileName(path);
470 List<AbstractFile> files = null;
471 if (parentPath != null) {
472 if (!parentPath.equals(
"/")) {
473 parentPath = parentPath +
"/";
476 files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='%s' AND name='%s'",
477 dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) :
""));
479 files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
482 if (files == null || files.isEmpty()) {
483 AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
484 return skCase.addVirtualDirectory(parent.getId(), folderName);
502 private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName,
long deletedTime)
throws TskCoreException {
503 skCase.addFileSystemFile(
504 recycleBinFile.getDataSourceObjectId(),
505 recycleBinFile.getFileSystemId(),
507 recycleBinFile.getMetaAddr(),
508 (int) recycleBinFile.getMetaSeq(),
509 recycleBinFile.getAttrType(),
510 recycleBinFile.getAttributeId(),
511 TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
512 (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
513 recycleBinFile.getSize(),
514 recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
526 String normalizeFilePath(String pathString) {
527 if (pathString == null || pathString.isEmpty()) {
531 Path path = Paths.get(pathString);
532 int nameCount = path.getNameCount();
534 String rootless =
"/" + path.subpath(0, nameCount);
535 return rootless.replace(
"\\",
"/");
550 String getFileName(String filePath) {
551 Path fileNamePath = Paths.get(filePath).getFileName();
552 if (fileNamePath != null) {
553 return fileNamePath.toString();
565 String getParentPath(String path) {
566 Path parentPath = Paths.get(path).getParent();
567 if (parentPath != null) {
568 return normalizeFilePath(parentPath.toString());
576 final class RecycledFileMetaData {
578 private final long fileSize;
579 private final long deletedTimeStamp;
580 private final String fileName;
589 RecycledFileMetaData(Long fileSize,
long deletedTimeStamp, String fileName) {
590 this.fileSize = fileSize;
591 this.deletedTimeStamp = deletedTimeStamp;
592 this.fileName = fileName;
609 long getDeletedTimeStamp() {
610 return deletedTimeStamp;
619 String getFullWindowsPath() {
620 return fileName.trim();