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;
82 "ExtractRecycleBin_module_name=Recycle Bin"
85 super(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", context.getJobId());
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 for(OsAccount account: tskCase.getOsAccountManager().getOsAccounts(((DataSource)dataSource).getHost())) {
342 Optional<String> userName = account.getLoginName();
343 userNameMap.put(account.getName(), userName.isPresent() ? userName.get() :
"");
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<>();
363 for (AbstractFile rFile : rFiles) {
364 String fileName = rFile.getName();
365 List<AbstractFile> fileList = fileMap.get(fileName);
367 if (fileList == null) {
368 fileList =
new ArrayList<>();
369 fileMap.put(fileName, fileList);
386 private String getUserIDFromPath(String iFileParentPath) {
387 int index = iFileParentPath.indexOf(
'-') - 1;
389 return (iFileParentPath.substring(index)).replace(
"/",
"");
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())));
410 "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin"
417 private void createRecycleBinArtifactType() throws TskCoreException {
419 tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name());
420 }
catch (BlackboardException ex) {
421 throw new TskCoreException(String.format(
"An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
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);
459 private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path)
throws TskCoreException {
461 String parentPath = getParentPath(path);
462 String folderName = getFileName(path);
464 List<AbstractFile> files = null;
465 if (parentPath != null) {
466 if (!parentPath.equals(
"/")) {
467 parentPath = parentPath +
"/";
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) :
""));
473 files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
476 if (files == null || files.isEmpty()) {
477 AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
478 return skCase.addVirtualDirectory(parent.getId(), folderName);
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(),
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,
520 String normalizeFilePath(String pathString) {
521 if (pathString == null || pathString.isEmpty()) {
525 Path path = Paths.get(pathString);
526 int nameCount = path.getNameCount();
528 String rootless =
"/" + path.subpath(0, nameCount);
529 return rootless.replace(
"\\",
"/");
544 String getFileName(String filePath) {
545 Path fileNamePath = Paths.get(filePath).getFileName();
546 if (fileNamePath != null) {
547 return fileNamePath.toString();
559 String getParentPath(String path) {
560 Path parentPath = Paths.get(path).getParent();
561 if (parentPath != null) {
562 return normalizeFilePath(parentPath.toString());
570 final class RecycledFileMetaData {
572 private final long fileSize;
573 private final long deletedTimeStamp;
574 private final String fileName;
583 RecycledFileMetaData(Long fileSize,
long deletedTimeStamp, String fileName) {
584 this.fileSize = fileSize;
585 this.deletedTimeStamp = deletedTimeStamp;
586 this.fileName = fileName;
603 long getDeletedTimeStamp() {
604 return deletedTimeStamp;
613 String getFullWindowsPath() {
614 return fileName.trim();