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.Optional;
39 import java.util.logging.Level;
40 import org.joda.time.Instant;
41 import org.openide.util.NbBundle.Messages;
49 import org.
sleuthkit.datamodel.Blackboard.BlackboardException;
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;
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;
80 private final IngestJobContext context;
83 "ExtractRecycleBin_module_name=Recycle Bin Analyzer"
85 ExtractRecycleBin(IngestJobContext context) {
86 super(Bundle.ExtractRecycleBin_module_name(), context);
87 this.context = context;
91 void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
95 createRecycleBinArtifactType();
96 }
catch (TskCoreException ex) {
97 logger.log(Level.WARNING, String.format(
"%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
100 BlackboardArtifact.Type recycleBinArtifactType;
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);
111 Map<String, String> userNameMap;
113 userNameMap = makeUserNameMap(dataSource);
114 }
catch (TskCoreException ex) {
115 logger.log(Level.WARNING,
"Unable to create OS Account user name map", ex);
118 userNameMap =
new HashMap<>();
121 FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
124 Map<String, List<AbstractFile>> rFileMap;
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);
133 List<AbstractFile> iFiles;
135 iFiles = fileManager.findFiles(dataSource,
"$I%", RECYCLE_BIN_DIR_NAME);
136 }
catch (TskCoreException ex) {
137 logger.log(Level.WARNING,
"Unable to find recycle bin I files.", ex);
141 String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(),
"recyclebin", context.getJobId());
144 for (AbstractFile iFile : iFiles) {
146 if (context.dataSourceIngestIsCancelled()) {
150 processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
153 (
new File(tempRARecycleBinPath)).
delete();
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();
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);
180 RecycledFileMetaData metaData;
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);
190 String userID = getUserIDFromPath(iFile.getParentPath());
191 String userName =
"";
192 if (!userID.isEmpty()) {
193 userName = userNameMap.get(userID);
201 String rFileName = iFile.getName().replace(
"$I",
"$R");
202 List<AbstractFile> rFiles = rFileMap.get(rFileName);
203 if (rFiles == null) {
206 SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
207 for (AbstractFile rFile : rFiles) {
208 if (context.dataSourceIngestIsCancelled()) {
212 if (iFile.getParentPath().equals(rFile.getParentPath())
213 && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
215 postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
220 if (rFile instanceof FsContent) {
224 AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
225 popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
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());
232 }
catch (TskCoreException ex) {
233 logger.log(Level.WARNING, String.format(
"Unable to add attributes to artifact %s", rFile.getName()), ex);
238 (
new File(tempFilePath)).
delete();
256 private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath,
long deletedTimeStamp)
throws TskCoreException {
257 if (recycledChildren == null) {
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);
309 private RecycledFileMetaData parseIFile(String iFilePath)
throws IOException {
311 byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
313 ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
314 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
316 long version = byteBuffer.getLong();
317 long fileSize = byteBuffer.getLong();
318 long timestamp = byteBuffer.getLong();
321 timestamp = Util.filetimeToMillis(timestamp) / 1000;
326 stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
328 int fileNameLength = byteBuffer.getInt() * 2;
329 stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
332 String fileName =
new String(stringBytes,
"UTF-16LE");
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);
349 private Map<String, String> makeUserNameMap(Content dataSource)
throws TskCoreException {
350 Map<String, String> userNameMap =
new HashMap<>();
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() :
"");
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<>();
374 for (AbstractFile rFile : rFiles) {
375 String fileName = rFile.getName();
376 List<AbstractFile> fileList = fileMap.get(fileName);
378 if (fileList == null) {
379 fileList =
new ArrayList<>();
380 fileMap.put(fileName, fileList);
397 private String getUserIDFromPath(String iFileParentPath) {
398 int index = iFileParentPath.indexOf(
'-') - 1;
400 return (iFileParentPath.substring(index)).replace(
"/",
"");
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())));
421 "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin"
428 private void createRecycleBinArtifactType() throws TskCoreException {
430 tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name());
431 }
catch (BlackboardException ex) {
432 throw new TskCoreException(String.format(
"An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
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);
470 private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path)
throws TskCoreException {
472 String parentPath = getParentPath(path);
473 String folderName = getFileName(path);
475 List<AbstractFile> files = null;
476 if (parentPath != null) {
477 if (!parentPath.equals(
"/")) {
478 parentPath = parentPath +
"/";
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) :
""));
484 files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
487 if (files == null || files.isEmpty()) {
488 AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
489 return skCase.addVirtualDirectory(parent.getId(), folderName);
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(),
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,
531 String normalizeFilePath(String pathString) {
532 if (pathString == null || pathString.isEmpty()) {
536 Path path = Paths.get(pathString);
537 int nameCount = path.getNameCount();
539 String rootless =
"/" + path.subpath(0, nameCount);
540 return rootless.replace(
"\\",
"/");
555 String getFileName(String filePath) {
556 Path fileNamePath = Paths.get(filePath).getFileName();
557 if (fileNamePath != null) {
558 return fileNamePath.toString();
570 String getParentPath(String path) {
571 Path parentPath = Paths.get(path).getParent();
572 if (parentPath != null) {
573 return normalizeFilePath(parentPath.toString());
581 final class RecycledFileMetaData {
583 private final long fileSize;
584 private final long deletedTimeStamp;
585 private final String fileName;
594 RecycledFileMetaData(Long fileSize,
long deletedTimeStamp, String fileName) {
595 this.fileSize = fileSize;
596 this.deletedTimeStamp = deletedTimeStamp;
597 this.fileName = fileName;
614 long getDeletedTimeStamp() {
615 return deletedTimeStamp;
624 String getFullWindowsPath() {
625 return fileName.trim();