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.Arrays;
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;
62 final class AddLogicalImageTask
implements Runnable {
79 FileId(
long dataSourceId,
long fileId) {
89 long getDataSourceId() {
103 private final static Logger LOGGER = Logger.getLogger(AddLogicalImageTask.class.getName());
104 private final static String SEARCH_RESULTS_TXT =
"SearchResults.txt";
105 private final static String USERS_TXT =
"_users.txt";
106 private final static String MODULE_NAME =
"Logical Imager";
107 private final static String ROOT_STR =
"root";
108 private final static String VHD_EXTENSION =
".vhd";
109 private final static int REPORT_PROGRESS_INTERVAL = 100;
110 private final static int POST_ARTIFACT_INTERVAL = 1000;
111 private final String deviceId;
112 private final String timeZone;
113 private final File src;
114 private final File dest;
115 private final Host host;
116 private final DataSourceProcessorCallback callback;
117 private final DataSourceProcessorProgressMonitor progressMonitor;
118 private final Blackboard blackboard;
119 private final Case currentCase;
121 private volatile boolean cancelled;
122 private volatile boolean createVHD;
123 private long totalFiles;
124 private Map<String, Long> imagePathToObjIdMap;
126 private final Object addMultipleImagesLock;
127 @GuardedBy(
"addMultipleImagesLock")
128 private AddMultipleImagesTask addMultipleImagesTask = null;
130 AddLogicalImageTask(String deviceId,
132 File src, File dest, Host host,
133 DataSourceProcessorProgressMonitor progressMonitor,
134 DataSourceProcessorCallback callback
135 ) throws NoCurrentCaseException {
136 this.deviceId = deviceId;
137 this.timeZone = timeZone;
141 this.progressMonitor = progressMonitor;
142 this.callback = callback;
143 this.currentCase = Case.getCurrentCase();
144 this.blackboard = this.currentCase.getServices().getArtifactsBlackboard();
145 this.addMultipleImagesLock =
new Object();
153 "# {0} - src",
"# {1} - dest",
"AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}",
154 "AddLogicalImageTask.doneCopying=Done copying",
155 "# {0} - src",
"# {1} - dest",
"AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}",
156 "# {0} - file",
"AddLogicalImageTask.addingToReport=Adding {0} to report",
157 "# {0} - file",
"AddLogicalImageTask.doneAddingToReport=Done adding {0} to report",
158 "AddLogicalImageTask.ingestionCancelled=Ingestion cancelled",
159 "# {0} - file",
"AddLogicalImageTask.failToGetCanonicalPath=Fail to get canonical path for {0}",
160 "# {0} - sparseImageDirectory",
"AddLogicalImageTask.directoryDoesNotContainSparseImage=Directory {0} does not contain any images",
161 "AddLogicalImageTask.noCurrentCase=No current case",
162 "AddLogicalImageTask.addingInterestingFiles=Adding search results as interesting files",
163 "AddLogicalImageTask.doneAddingInterestingFiles=Done adding search results as interesting files",
164 "# {0} - SearchResults.txt",
"# {1} - directory",
"AddLogicalImageTask.cannotFindFiles=Cannot find {0} in {1}",
165 "# {0} - reason",
"AddLogicalImageTask.failedToAddInterestingFiles=Failed to add interesting files: {0}",
166 "AddLogicalImageTask.addingExtractedFiles=Adding extracted files",
167 "AddLogicalImageTask.doneAddingExtractedFiles=Done adding extracted files",
168 "# {0} - reason",
"AddLogicalImageTask.failedToGetTotalFilesCount=Failed to get total files count: {0}",
169 "AddLogicalImageTask.addImageCancelled=Add image cancelled"
173 List<String> errorList =
new ArrayList<>();
174 List<Content> emptyDataSources =
new ArrayList<>();
177 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString()));
178 FileUtils.copyDirectory(src, dest);
179 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneCopying());
180 }
catch (IOException ex) {
182 String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString());
189 deleteDestinationDirectory();
190 errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
191 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
196 String resultsFilename;
197 if (Paths.get(dest.toString(), SEARCH_RESULTS_TXT).toFile().exists()) {
198 resultsFilename = SEARCH_RESULTS_TXT;
200 errorList.add(Bundle.AddLogicalImageTask_cannotFindFiles(SEARCH_RESULTS_TXT, dest.toString()));
201 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
205 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(resultsFilename));
206 String status = addReport(Paths.get(dest.toString(), resultsFilename), resultsFilename +
" " + src.getName());
207 if (status != null) {
208 errorList.add(status);
209 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
212 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(resultsFilename));
215 File[] userFiles = dest.listFiles(
new FilenameFilter() {
217 public boolean accept(File dir, String name) {
218 return name.endsWith(USERS_TXT);
222 for (File userFile : userFiles) {
223 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(userFile.getName()));
224 status = addReport(userFile.toPath(), userFile.getName() +
" " + src.getName());
225 if (status != null) {
226 errorList.add(status);
227 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
230 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(userFile.getName()));
234 List<String> imagePaths =
new ArrayList<>();
235 for (File f : dest.listFiles()) {
236 if (f.getName().endsWith(VHD_EXTENSION)) {
238 imagePaths.add(f.getCanonicalPath());
239 }
catch (IOException ioe) {
240 String msg = Bundle.AddLogicalImageTask_failToGetCanonicalPath(f.getName());
242 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
248 Path resultsPath = Paths.get(dest.toString(), resultsFilename);
250 totalFiles = Files.lines(resultsPath).count() - 1;
251 }
catch (IOException ex) {
252 errorList.add(Bundle.AddLogicalImageTask_failedToGetTotalFilesCount(ex.getMessage()));
253 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
257 List<Content> newDataSources =
new ArrayList<>();
258 Map<String, List<FileId>> interestingFileMap =
new HashMap<>();
260 if (imagePaths.isEmpty()) {
263 File root = Paths.get(dest.toString(), ROOT_STR).toFile();
264 if (root.exists() && root.isDirectory()) {
265 imagePaths.add(root.getAbsolutePath());
267 String msg = Bundle.AddLogicalImageTask_directoryDoesNotContainSparseImage(dest);
269 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
274 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingExtractedFiles());
275 interestingFileMap = addExtractedFiles(dest, resultsPath, host, newDataSources);
276 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingExtractedFiles());
277 }
catch (IOException | TskCoreException ex) {
278 errorList.add(ex.getMessage());
279 LOGGER.log(Level.SEVERE, String.format(
"Failed to add datasource: %s", ex.getMessage()), ex);
280 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
287 synchronized (addMultipleImagesLock) {
289 LOGGER.log(Level.SEVERE,
"Add VHD cancelled");
290 errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
291 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
294 addMultipleImagesTask =
new AddMultipleImagesTask(deviceId, imagePaths, timeZone, host, progressMonitor);
296 addMultipleImagesTask.run();
297 if (addMultipleImagesTask.getResult() == DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS) {
298 LOGGER.log(Level.SEVERE,
"Failed to add VHD datasource");
299 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, addMultipleImagesTask.getErrorMessages(), emptyDataSources);
303 interestingFileMap = getInterestingFileMapForVHD(Paths.get(dest.toString(), resultsFilename));
304 }
catch (TskCoreException | IOException ex) {
305 errorList.add(Bundle.AddLogicalImageTask_failedToAddInterestingFiles(ex.getMessage()));
306 LOGGER.log(Level.SEVERE,
"Failed to add interesting files", ex);
307 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NONCRITICAL_ERRORS, errorList, emptyDataSources);
310 }
catch (NoCurrentCaseException ex) {
311 String msg = Bundle.AddLogicalImageTask_noCurrentCase();
313 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
321 deleteDestinationDirectory();
323 errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
324 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
329 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingInterestingFiles());
330 addInterestingFiles(interestingFileMap);
331 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingInterestingFiles());
333 callback.done(addMultipleImagesTask.getResult(), addMultipleImagesTask.getErrorMessages(), addMultipleImagesTask.getNewDataSources());
335 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, errorList, newDataSources);
337 }
catch (IOException | TskCoreException ex) {
338 errorList.add(Bundle.AddLogicalImageTask_failedToAddInterestingFiles(ex.getMessage()));
339 LOGGER.log(Level.SEVERE,
"Failed to add interesting files", ex);
340 callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NONCRITICAL_ERRORS, errorList, emptyDataSources);
354 "# {0} - file",
"# {1} - exception message",
"AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1}"
356 private String addReport(Path reportPath, String reportName) {
357 if (!reportPath.toFile().exists()) {
361 Case.getCurrentCase().addReport(reportPath.toString(),
"LogicalImager", reportName);
363 }
catch (TskCoreException ex) {
364 String msg = Bundle.AddLogicalImageTask_failedToAddReport(reportPath.toString(), ex.getMessage());
365 LOGGER.log(Level.SEVERE, String.format(
"Failed to add report %s. Reason= %s", reportPath.toString(), ex.getMessage()), ex);
375 LOGGER.log(Level.WARNING,
"AddLogicalImageTask cancelled, processing may be incomplete");
376 synchronized (addMultipleImagesLock) {
378 if (addMultipleImagesTask != null) {
379 addMultipleImagesTask.cancelTask();
384 private Map<String, Long> imagePathsToDataSourceObjId(Map<Long, List<String>> imagePaths) {
385 Map<String, Long> imagePathToObjId =
new HashMap<>();
386 for (Map.Entry<Long, List<String>> entry : imagePaths.entrySet()) {
387 Long key = entry.getKey();
388 List<String> names = entry.getValue();
389 for (String name : names) {
390 imagePathToObjId.put(name, key);
393 return imagePathToObjId;
397 "# {0} - line number",
"# {1} - fields length",
"# {2} - expected length",
"AddLogicalImageTask.notEnoughFields=File does not contain enough fields at line {0}, got {1}, expecting {2}",
398 "# {0} - target image path",
"AddLogicalImageTask.cannotFindDataSourceObjId=Cannot find obj_id in tsk_image_names for {0}",
399 "# {0} - file number",
"# {1} - total files",
"AddLogicalImageTask.addingInterestingFile=Adding interesting files ({0}/{1})",
400 "AddLogicalImageTask.logicalImagerResults=Logical Imager results"
402 private void addInterestingFiles(Map<String, List<FileId>> interestingFileMap) throws IOException, TskCoreException {
404 List<BlackboardArtifact> artifacts =
new ArrayList<>();
406 Iterator<Map.Entry<String, List<FileId>>> iterator = interestingFileMap.entrySet().iterator();
407 while (iterator.hasNext()) {
415 Map.Entry<String, List<FileId>> entry = iterator.next();
416 String key = entry.getKey();
418 String[] split = key.split(
"\t");
421 List<FileId> fileIds = entry.getValue();
422 for (FileId fileId : fileIds) {
424 postArtifacts(artifacts);
427 if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
428 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingInterestingFile(lineNumber, totalFiles));
430 if (lineNumber % POST_ARTIFACT_INTERVAL == 0) {
431 postArtifacts(artifacts);
434 addInterestingFileToArtifacts(fileId.getFileId(), fileId.getDataSourceId(), Bundle.AddLogicalImageTask_logicalImagerResults(), ruleName, artifacts);
439 postArtifacts(artifacts);
442 private void addInterestingFileToArtifacts(
long fileId,
long dataSourceId, String ruleSetName, String ruleName, List<BlackboardArtifact> artifacts)
throws TskCoreException {
443 BlackboardArtifact artifact;
445 artifact = this.blackboard.newAnalysisResult(
446 BlackboardArtifact.Type.TSK_INTERESTING_ITEM, fileId, dataSourceId,
447 Score.SCORE_LIKELY_NOTABLE,
448 null, ruleSetName, null,
450 new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, ruleSetName),
451 new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, MODULE_NAME, ruleName)
453 .getAnalysisResult();
454 }
catch (Blackboard.BlackboardException ex) {
455 throw new TskCoreException(
"Unable to create analysis result.", ex);
458 artifacts.add(artifact);
462 "# {0} - file number",
"# {1} - total files",
"AddLogicalImageTask.searchingInterestingFile=Searching for interesting files ({0}/{1})"
464 private Map<String, List<FileId>> getInterestingFileMapForVHD(Path resultsPath)
throws TskCoreException, IOException {
465 Map<Long, List<String>> objIdToimagePathsMap = currentCase.getSleuthkitCase().getImagePaths();
466 imagePathToObjIdMap = imagePathsToDataSourceObjId(objIdToimagePathsMap);
467 Map<String, List<FileId>> interestingFileMap =
new HashMap<>();
469 try (BufferedReader br =
new BufferedReader(
new InputStreamReader(
470 new FileInputStream(resultsPath.toFile()),
"UTF8"))) {
474 while ((line = br.readLine()) != null) {
480 String[] fields = line.split(
"\t", -1);
481 if (fields.length != 14) {
482 throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 14));
484 String vhdFilename = fields[0];
486 String fileMetaAddressStr = fields[2];
488 String ruleSetName = fields[4];
489 String ruleName = fields[5];
491 String filename = fields[7];
492 String parentPath = fields[8];
494 if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
495 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_searchingInterestingFile(lineNumber, totalFiles));
498 String query = makeQuery(vhdFilename, fileMetaAddressStr, parentPath, filename);
499 List<AbstractFile> matchedFiles = Case.getCurrentCase().getSleuthkitCase().findAllFilesWhere(query);
500 List<FileId> fileIds =
new ArrayList<>();
501 for (AbstractFile file : matchedFiles) {
502 fileIds.add(
new FileId(file.getDataSourceObjectId(), file.getId()));
504 String key = String.format(
"%s\t%s", ruleSetName, ruleName);
505 interestingFileMap.computeIfAbsent(key, (k) ->
new ArrayList<>())
511 return interestingFileMap;
514 private void postArtifacts(List<BlackboardArtifact> artifacts) {
516 blackboard.postArtifacts(artifacts, MODULE_NAME, null);
517 }
catch (Blackboard.BlackboardException ex) {
518 LOGGER.log(Level.SEVERE,
"Unable to post artifacts to blackboard", ex);
523 "# {0} - file number",
"# {1} - total files",
"AddLogicalImageTask.addingExtractedFile=Adding extracted files ({0}/{1})"
525 private Map<String, List<FileId>> addExtractedFiles(File src, Path resultsPath, Host host, List<Content> newDataSources)
throws TskCoreException, IOException {
526 SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
527 SleuthkitCase.CaseDbTransaction trans = null;
528 Map<String, List<FileId>> interestingFileMap =
new HashMap<>();
531 trans = skCase.beginTransaction();
532 LocalFilesDataSource localFilesDataSource = skCase.addLocalFilesDataSource(deviceId, this.src.getName(), timeZone, host, trans);
533 LocalFileImporter fileImporter =
new LocalFileImporter(skCase, trans);
535 try (BufferedReader br =
new BufferedReader(
new InputStreamReader(
536 new FileInputStream(resultsPath.toFile()),
"UTF8"))) {
540 while ((line = br.readLine()) != null) {
542 rollbackTransaction(trans);
543 return new HashMap<>();
545 String[] fields = line.split(
"\t", -1);
546 if (fields.length != 14) {
547 rollbackTransaction(trans);
548 throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 14));
550 String vhdFilename = fields[0];
554 String ruleSetName = fields[4];
555 String ruleName = fields[5];
557 String filename = fields[7];
558 String parentPath = fields[8];
559 String extractedFilePath = fields[9];
560 String crtime = fields[10];
561 String mtime = fields[11];
562 String atime = fields[12];
563 String ctime = fields[13];
564 parentPath = ROOT_STR +
"/" + vhdFilename +
"/" + parentPath;
566 if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
567 progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingExtractedFile(lineNumber, totalFiles));
571 AbstractFile fileAdded = fileImporter.addLocalFile(
572 Paths.get(src.toString(), extractedFilePath).toFile(),
575 Long.parseLong(ctime),
576 Long.parseLong(crtime),
577 Long.parseLong(atime),
578 Long.parseLong(mtime),
579 localFilesDataSource);
580 String key = String.format(
"%s\t%s", ruleSetName, ruleName);
582 long dataSourceId = fileAdded.getDataSourceObjectId();
583 long fileId = fileAdded.getId();
584 interestingFileMap.computeIfAbsent(key, (k) ->
new ArrayList<>())
585 .add(
new FileId(dataSourceId, fileId));
590 newDataSources.add(localFilesDataSource);
591 return interestingFileMap;
593 }
catch (NumberFormatException | TskCoreException ex) {
594 LOGGER.log(Level.SEVERE,
"Error adding extracted files", ex);
595 rollbackTransaction(trans);
596 throw new TskCoreException(
"Error adding extracted files", ex);
600 private void rollbackTransaction(SleuthkitCase.CaseDbTransaction trans) throws TskCoreException {
604 }
catch (TskCoreException ex) {
605 LOGGER.log(Level.SEVERE, String.format(
"Failed to rollback transaction: %s", ex.getMessage()), ex);
610 private boolean deleteDestinationDirectory() {
612 FileUtils.deleteDirectory(dest);
613 LOGGER.log(Level.INFO, String.format(
"Cancellation: Deleted directory %s", dest.toString()));
615 }
catch (IOException ex) {
616 LOGGER.log(Level.WARNING, String.format(
"Cancellation: Failed to delete directory %s", dest.toString()), ex);
621 String makeQuery(String vhdFilename, String fileMetaAddressStr, String parentPath, String filename)
throws TskCoreException {
623 String targetImagePath = Paths.get(dest.toString(), vhdFilename).toString();
624 Long dataSourceObjId = imagePathToObjIdMap.get(targetImagePath);
625 if (dataSourceObjId == null) {
626 throw new TskCoreException(Bundle.AddLogicalImageTask_cannotFindDataSourceObjId(targetImagePath));
628 query = String.format(
"data_source_obj_id = '%s' AND meta_addr = '%s' AND name = '%s'",
629 dataSourceObjId.toString(), fileMetaAddressStr, filename.replace(
"'",
"''"));