19 package org.sleuthkit.autopsy.logicalimager.dsp;
21 import java.io.BufferedReader;
23 import java.io.FileInputStream;
24 import java.io.FilenameFilter;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
36 import java.util.logging.Level;
37 import javax.annotation.concurrent.GuardedBy;
38 import org.apache.commons.io.FileUtils;
39 import org.openide.util.NbBundle.Messages;
60 final class AddLogicalImageTask
implements Runnable {
63 private final static String SEARCH_RESULTS_TXT =
"SearchResults.txt";
64 private final static String USERS_TXT =
"_users.txt";
65 private final static String MODULE_NAME =
"Logical Imager";
66 private final static String ROOT_STR =
"root";
67 private final static String VHD_EXTENSION =
".vhd";
68 private final static int REPORT_PROGRESS_INTERVAL = 100;
69 private final static int POST_ARTIFACT_INTERVAL = 1000;
70 private final String deviceId;
71 private final String timeZone;
72 private final File src;
73 private final File dest;
76 private final Blackboard blackboard;
77 private final Case currentCase;
79 private volatile boolean cancelled;
80 private volatile boolean createVHD;
81 private long totalFiles;
82 private Map<String, Long> imagePathToObjIdMap;
84 private final Object addMultipleImagesLock;
85 @GuardedBy(
"addMultipleImagesLock")
86 private AddMultipleImagesTask addMultipleImagesTask = null;
88 AddLogicalImageTask(String deviceId,
94 this.deviceId = deviceId;
95 this.timeZone = timeZone;
98 this.progressMonitor = progressMonitor;
99 this.callback = callback;
102 this.addMultipleImagesLock =
new Object();
110 "# {0} - src",
"# {1} - dest",
"AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}",
111 "AddLogicalImageTask.doneCopying=Done copying",
112 "# {0} - src",
"# {1} - dest",
"AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}",
113 "# {0} - file",
"AddLogicalImageTask.addingToReport=Adding {0} to report",
114 "# {0} - file",
"AddLogicalImageTask.doneAddingToReport=Done adding {0} to report",
115 "AddLogicalImageTask.ingestionCancelled=Ingestion cancelled",
116 "# {0} - file",
"AddLogicalImageTask.failToGetCanonicalPath=Fail to get canonical path for {0}",
117 "# {0} - sparseImageDirectory",
"AddLogicalImageTask.directoryDoesNotContainSparseImage=Directory {0} does not contain any images",
118 "AddLogicalImageTask.noCurrentCase=No current case",
119 "AddLogicalImageTask.addingInterestingFiles=Adding search results as interesting files",
120 "AddLogicalImageTask.doneAddingInterestingFiles=Done adding search results as interesting files",
121 "# {0} - SearchResults.txt",
"# {1} - directory",
"AddLogicalImageTask.cannotFindFiles=Cannot find {0} in {1}",
122 "# {0} - reason",
"AddLogicalImageTask.failedToAddInterestingFiles=Failed to add interesting files: {0}",
123 "AddLogicalImageTask.addingExtractedFiles=Adding extracted files",
124 "AddLogicalImageTask.doneAddingExtractedFiles=Done adding extracted files",
125 "# {0} - reason",
"AddLogicalImageTask.failedToGetTotalFilesCount=Failed to get total files count: {0}",
126 "AddLogicalImageTask.addImageCancelled=Add image cancelled"
130 List<String> errorList =
new ArrayList<>();
131 List<Content> emptyDataSources =
new ArrayList<>();
134 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString()));
135 FileUtils.copyDirectory(src, dest);
136 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_doneCopying());
137 }
catch (IOException ex) {
139 String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString());
146 deleteDestinationDirectory();
147 errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
153 String resultsFilename;
154 if (Paths.get(dest.toString(), SEARCH_RESULTS_TXT).toFile().exists()) {
155 resultsFilename = SEARCH_RESULTS_TXT;
157 errorList.add(Bundle.AddLogicalImageTask_cannotFindFiles(SEARCH_RESULTS_TXT, dest.toString()));
162 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_addingToReport(resultsFilename));
163 String status = addReport(Paths.get(dest.toString(), resultsFilename), resultsFilename +
" " + src.getName());
164 if (status != null) {
165 errorList.add(status);
169 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(resultsFilename));
172 File[] userFiles = dest.listFiles(
new FilenameFilter() {
174 public boolean accept(File dir, String name) {
175 return name.endsWith(USERS_TXT);
179 for (File userFile : userFiles) {
180 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_addingToReport(userFile.getName()));
181 status = addReport(userFile.toPath(), userFile.getName() +
" " + src.getName());
182 if (status != null) {
183 errorList.add(status);
187 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(userFile.getName()));
191 List<String> imagePaths =
new ArrayList<>();
192 for (File f : dest.listFiles()) {
193 if (f.getName().endsWith(VHD_EXTENSION)) {
195 imagePaths.add(f.getCanonicalPath());
196 }
catch (IOException ioe) {
197 String msg = Bundle.AddLogicalImageTask_failToGetCanonicalPath(f.getName());
205 Path resultsPath = Paths.get(dest.toString(), resultsFilename);
207 totalFiles = Files.lines(resultsPath).count() - 1;
208 }
catch (IOException ex) {
209 errorList.add(Bundle.AddLogicalImageTask_failedToGetTotalFilesCount(ex.getMessage()));
214 List<Content> newDataSources =
new ArrayList<>();
215 Map<String, List<Long>> interestingFileMap =
new HashMap<>();
217 if (imagePaths.isEmpty()) {
220 File root = Paths.get(dest.toString(), ROOT_STR).toFile();
221 if (root.exists() && root.isDirectory()) {
222 imagePaths.add(root.getAbsolutePath());
224 String msg = Bundle.AddLogicalImageTask_directoryDoesNotContainSparseImage(dest);
231 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_addingExtractedFiles());
232 interestingFileMap = addExtractedFiles(dest, resultsPath, newDataSources);
233 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_doneAddingExtractedFiles());
234 }
catch (IOException | TskCoreException ex) {
235 errorList.add(ex.getMessage());
236 LOGGER.log(Level.SEVERE, String.format(
"Failed to add datasource: %s", ex.getMessage()), ex);
244 synchronized (addMultipleImagesLock) {
246 LOGGER.log(Level.SEVERE,
"Add VHD cancelled");
247 errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
251 addMultipleImagesTask =
new AddMultipleImagesTask(deviceId, imagePaths, timeZone , progressMonitor);
253 addMultipleImagesTask.run();
255 LOGGER.log(Level.SEVERE,
"Failed to add VHD datasource");
260 interestingFileMap = getInterestingFileMapForVHD(Paths.get(dest.toString(), resultsFilename));
261 }
catch (TskCoreException | IOException ex) {
262 errorList.add(Bundle.AddLogicalImageTask_failedToAddInterestingFiles(ex.getMessage()));
263 LOGGER.log(Level.SEVERE,
"Failed to add interesting files", ex);
268 String msg = Bundle.AddLogicalImageTask_noCurrentCase();
278 deleteDestinationDirectory();
280 errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
286 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_addingInterestingFiles());
287 addInterestingFiles(interestingFileMap);
288 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_doneAddingInterestingFiles());
290 callback.
done(addMultipleImagesTask.getResult(), addMultipleImagesTask.getErrorMessages(), addMultipleImagesTask.getNewDataSources());
294 }
catch (IOException | TskCoreException ex) {
295 errorList.add(Bundle.AddLogicalImageTask_failedToAddInterestingFiles(ex.getMessage()));
296 LOGGER.log(Level.SEVERE,
"Failed to add interesting files", ex);
311 "# {0} - file",
"# {1} - exception message",
"AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1}"
313 private String addReport(Path reportPath, String reportName) {
314 if (!reportPath.toFile().exists()) {
320 }
catch (TskCoreException ex) {
321 String msg = Bundle.AddLogicalImageTask_failedToAddReport(reportPath.toString(), ex.getMessage());
322 LOGGER.log(Level.SEVERE, String.format(
"Failed to add report %s. Reason= %s", reportPath.toString(), ex.getMessage()), ex);
332 LOGGER.log(Level.WARNING,
"AddLogicalImageTask cancelled, processing may be incomplete");
333 synchronized (addMultipleImagesLock) {
335 if (addMultipleImagesTask != null) {
336 addMultipleImagesTask.cancelTask();
341 private Map<String, Long> imagePathsToDataSourceObjId(Map<Long, List<String>> imagePaths) {
342 Map<String, Long> imagePathToObjId =
new HashMap<>();
343 for (Map.Entry<Long, List<String>> entry : imagePaths.entrySet()) {
344 Long key = entry.getKey();
345 List<String> names = entry.getValue();
346 for (String name : names) {
347 imagePathToObjId.put(name, key);
350 return imagePathToObjId;
354 "# {0} - line number",
"# {1} - fields length",
"# {2} - expected length",
"AddLogicalImageTask.notEnoughFields=File does not contain enough fields at line {0}, got {1}, expecting {2}",
355 "# {0} - target image path",
"AddLogicalImageTask.cannotFindDataSourceObjId=Cannot find obj_id in tsk_image_names for {0}",
356 "# {0} - file number",
"# {1} - total files",
"AddLogicalImageTask.addingInterestingFile=Adding interesting files ({0}/{1})",
357 "AddLogicalImageTask.logicalImagerResults=Logical Imager results"
359 private void addInterestingFiles(Map<String, List<Long>> interestingFileMap)
throws IOException, TskCoreException {
361 List<BlackboardArtifact> artifacts =
new ArrayList<>();
363 Iterator<Map.Entry<String, List<Long>>> iterator = interestingFileMap.entrySet().iterator();
364 while (iterator.hasNext()) {
372 Map.Entry<String, List<Long>> entry = iterator.next();
373 String key = entry.getKey();
375 String[] split = key.split(
"\t");
378 List<Long> fileIds = entry.getValue();
379 for (Long fileId: fileIds) {
381 postArtifacts(artifacts);
384 if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
385 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_addingInterestingFile(lineNumber, totalFiles));
387 if (lineNumber % POST_ARTIFACT_INTERVAL == 0) {
388 postArtifacts(artifacts);
391 addInterestingFileToArtifacts(fileId, Bundle.AddLogicalImageTask_logicalImagerResults(), ruleName, artifacts);
396 postArtifacts(artifacts);
399 private void addInterestingFileToArtifacts(
long fileId, String ruleSetName, String ruleName, List<BlackboardArtifact> artifacts)
throws TskCoreException {
400 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
401 BlackboardAttribute setNameAttribute =
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, ruleSetName);
402 attributes.add(setNameAttribute);
403 BlackboardAttribute ruleNameAttribute =
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, MODULE_NAME, ruleName);
404 attributes.add(ruleNameAttribute);
405 BlackboardArtifact artifact = this.currentCase.
getSleuthkitCase().newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, fileId);
406 artifact.addAttributes(attributes);
407 artifacts.add(artifact);
411 "# {0} - file number",
"# {1} - total files",
"AddLogicalImageTask.searchingInterestingFile=Searching for interesting files ({0}/{1})"
413 private Map<String, List<Long>> getInterestingFileMapForVHD(Path resultsPath)
throws TskCoreException, IOException {
414 Map<Long, List<String>> objIdToimagePathsMap = currentCase.
getSleuthkitCase().getImagePaths();
415 imagePathToObjIdMap = imagePathsToDataSourceObjId(objIdToimagePathsMap);
416 Map<String, List<Long>> interestingFileMap =
new HashMap<>();
418 try (BufferedReader br =
new BufferedReader(
new InputStreamReader(
419 new FileInputStream(resultsPath.toFile()),
"UTF8"))) {
423 while ((line = br.readLine()) != null) {
429 String[] fields = line.split(
"\t", -1);
430 if (fields.length != 14) {
431 throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 14));
433 String vhdFilename = fields[0];
435 String fileMetaAddressStr = fields[2];
437 String ruleSetName = fields[4];
438 String ruleName = fields[5];
440 String filename = fields[7];
441 String parentPath = fields[8];
443 if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
444 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_searchingInterestingFile(lineNumber, totalFiles));
447 String query = makeQuery(vhdFilename, fileMetaAddressStr, parentPath, filename);
449 List<Long> fileIds =
new ArrayList<>();
450 for (AbstractFile file : matchedFiles) {
451 fileIds.add(file.getId());
453 String key = String.format(
"%s\t%s", ruleSetName, ruleName);
454 if (interestingFileMap.containsKey(key)) {
455 interestingFileMap.get(key).addAll(fileIds);
457 interestingFileMap.put(key, fileIds);
462 return interestingFileMap;
465 private void postArtifacts(List<BlackboardArtifact> artifacts) {
468 blackboard.postArtifacts(artifacts, MODULE_NAME);
469 }
catch (Blackboard.BlackboardException ex) {
470 LOGGER.log(Level.SEVERE,
"Unable to post artifacts to blackboard", ex);
475 "# {0} - file number",
"# {1} - total files",
"AddLogicalImageTask.addingExtractedFile=Adding extracted files ({0}/{1})"
477 private Map<String, List<Long>> addExtractedFiles(File src, Path resultsPath, List<Content> newDataSources)
throws TskCoreException, IOException {
479 SleuthkitCase.CaseDbTransaction trans = null;
480 Map<String, List<Long>> interestingFileMap =
new HashMap<>();
483 trans = skCase.beginTransaction();
484 LocalFilesDataSource localFilesDataSource = skCase.addLocalFilesDataSource(deviceId, this.src.getName(), timeZone, trans);
487 try (BufferedReader br =
new BufferedReader(
new InputStreamReader(
488 new FileInputStream(resultsPath.toFile()),
"UTF8"))) {
492 while ((line = br.readLine()) != null) {
494 rollbackTransaction(trans);
495 return new HashMap<>();
497 String[] fields = line.split(
"\t", -1);
498 if (fields.length != 14) {
499 rollbackTransaction(trans);
500 throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 14));
502 String vhdFilename = fields[0];
506 String ruleSetName = fields[4];
507 String ruleName = fields[5];
509 String filename = fields[7];
510 String parentPath = fields[8];
511 String extractedFilePath = fields[9];
512 String crtime = fields[10];
513 String mtime = fields[11];
514 String atime = fields[12];
515 String ctime = fields[13];
516 parentPath = ROOT_STR +
"/" + vhdFilename +
"/" + parentPath;
518 if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
519 progressMonitor.
setProgressText(Bundle.AddLogicalImageTask_addingExtractedFile(lineNumber, totalFiles));
524 Paths.get(src.toString(), extractedFilePath).toFile(),
527 Long.parseLong(ctime),
528 Long.parseLong(crtime),
529 Long.parseLong(atime),
530 Long.parseLong(mtime),
531 localFilesDataSource);
532 String key = String.format(
"%s\t%s", ruleSetName, ruleName);
533 List<Long> value =
new ArrayList<>();
534 if (interestingFileMap.containsKey(key)) {
535 value = interestingFileMap.get(key);
537 value.add(fileAdded.getId());
538 interestingFileMap.put(key, value);
543 newDataSources.add(localFilesDataSource);
544 return interestingFileMap;
546 }
catch (NumberFormatException | TskCoreException ex) {
547 LOGGER.log(Level.SEVERE,
"Error adding extracted files", ex);
548 rollbackTransaction(trans);
549 throw new TskCoreException(
"Error adding extracted files", ex);
553 private void rollbackTransaction(SleuthkitCase.CaseDbTransaction trans)
throws TskCoreException {
557 }
catch (TskCoreException ex) {
558 LOGGER.log(Level.SEVERE, String.format(
"Failed to rollback transaction: %s", ex.getMessage()), ex);
563 private boolean deleteDestinationDirectory() {
565 FileUtils.deleteDirectory(dest);
566 LOGGER.log(Level.INFO, String.format(
"Cancellation: Deleted directory %s", dest.toString()));
568 }
catch (IOException ex) {
569 LOGGER.log(Level.WARNING, String.format(
"Cancellation: Failed to delete directory %s", dest.toString()), ex);
574 String makeQuery(String vhdFilename, String fileMetaAddressStr, String parentPath, String filename)
throws TskCoreException {
576 String targetImagePath = Paths.get(dest.toString(), vhdFilename).toString();
577 Long dataSourceObjId = imagePathToObjIdMap.get(targetImagePath);
578 if (dataSourceObjId == null) {
579 throw new TskCoreException(Bundle.AddLogicalImageTask_cannotFindDataSourceObjId(targetImagePath));
581 query = String.format(
"data_source_obj_id = '%s' AND meta_addr = '%s' AND name = '%s'",
582 dataSourceObjId.toString(), fileMetaAddressStr, filename.replace(
"'",
"''"));
org.sleuthkit.datamodel.Blackboard getArtifactsBlackboard()
void setProgressText(String text)
void addReport(String localPath, String srcModuleName, String reportName)
void done(DataSourceProcessorResult result, List< String > errList, List< Content > newDataSources)
AbstractFile addLocalFile(File fileOnDisk, String name, String parentPath, Long ctime, Long crtime, Long atime, Long mtime, DataSource dataSource)
SleuthkitCase getSleuthkitCase()
static Case getCurrentCase()
synchronized static Logger getLogger(String name)