Autopsy  4.5.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
SevenZipExtractor.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2014 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.modules.embeddedfileextractor;
20 
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.logging.Level;
32 import net.sf.sevenzipjbinding.ArchiveFormat;
33 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
34 import net.sf.sevenzipjbinding.ISequentialOutStream;
35 import net.sf.sevenzipjbinding.ISevenZipInArchive;
36 import net.sf.sevenzipjbinding.SevenZip;
37 import net.sf.sevenzipjbinding.SevenZipException;
38 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
39 import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
40 import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
41 import org.netbeans.api.progress.ProgressHandle;
42 import org.openide.util.NbBundle;
43 import org.openide.util.NbBundle.Messages;
57 import org.sleuthkit.datamodel.AbstractFile;
58 import org.sleuthkit.datamodel.BlackboardArtifact;
59 import org.sleuthkit.datamodel.BlackboardAttribute;
60 import org.sleuthkit.datamodel.DerivedFile;
61 import org.sleuthkit.datamodel.EncodedFileOutputStream;
62 import org.sleuthkit.datamodel.ReadContentInputStream;
63 import org.sleuthkit.datamodel.TskCoreException;
64 import org.sleuthkit.datamodel.TskData;
65 
66 class SevenZipExtractor {
67 
68  private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
69  private IngestServices services = IngestServices.getInstance();
70  private final IngestJobContext context;
71  private final FileTypeDetector fileTypeDetector;
72  static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // NON-NLS
73  //encryption type strings
74  private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
75  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
76  private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
77  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
78  //zip bomb detection
79  private static final int MAX_DEPTH = 4;
80  private static final int MAX_COMPRESSION_RATIO = 600;
81  private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
82  private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB
83  //counts archive depth
84  private ArchiveDepthCountTree archiveDepthCountTree;
85 
86  private String moduleDirRelative;
87  private String moduleDirAbsolute;
88 
89  private Blackboard blackboard;
90 
91  private String getLocalRootAbsPath(String uniqueArchiveFileName) {
92  return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
93  }
94 
99 
100  ZIP("application/zip"), //NON-NLS
101  SEVENZ("application/x-7z-compressed"), //NON-NLS
102  GZIP("application/gzip"), //NON-NLS
103  XGZIP("application/x-gzip"), //NON-NLS
104  XBZIP2("application/x-bzip2"), //NON-NLS
105  XTAR("application/x-tar"), //NON-NLS
106  XGTAR("application/x-gtar"),
107  XRAR("application/x-rar-compressed"); //NON-NLS
108 
109  private final String mimeType;
110 
111  SupportedArchiveExtractionFormats(final String mimeType) {
112  this.mimeType = mimeType;
113  }
114 
115  @Override
116  public String toString() {
117  return this.mimeType;
118  }
119  // TODO Expand to support more formats after upgrading Tika
120  }
121 
122  SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws SevenZipNativeInitializationException {
123  if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
124  SevenZip.initSevenZipFromPlatformJAR();
125  }
126  this.context = context;
127  this.fileTypeDetector = fileTypeDetector;
128  this.moduleDirRelative = moduleDirRelative;
129  this.moduleDirAbsolute = moduleDirAbsolute;
130  this.archiveDepthCountTree = new ArchiveDepthCountTree();
131  }
132 
141  boolean isSevenZipExtractionSupported(AbstractFile abstractFile) {
142  String abstractFileMimeType = fileTypeDetector.getMIMEType(abstractFile);
143  for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) {
144  if (s.toString().equals(abstractFileMimeType)) {
145  return true;
146  }
147  }
148  return false;
149  }
150 
163  private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) {
164  try {
165  final Long archiveItemSize = archiveFileItem.getSize();
166 
167  //skip the check for small files
168  if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
169  return false;
170  }
171 
172  final Long archiveItemPackedSize = archiveFileItem.getPackedSize();
173 
174  if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
175  logger.log(Level.WARNING, "Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", new Object[]{archiveFile.getName(), archiveFileItem.getPath()}); //NON-NLS
176  return false;
177  }
178 
179  int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
180 
181  if (cRatio >= MAX_COMPRESSION_RATIO) {
182  String itemName = archiveFileItem.getPath();
183  logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS
184  String msg = NbBundle.getMessage(SevenZipExtractor.class,
185  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
186  String path;
187  try {
188  path = archiveFile.getUniquePath();
189  } catch (TskCoreException ex) {
190  path = archiveFile.getParentPath() + archiveFile.getName();
191  }
192  String details = NbBundle.getMessage(SevenZipExtractor.class,
193  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
194  //MessageNotifyUtil.Notify.error(msg, details);
195  services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
196  return true;
197  } else {
198  return false;
199  }
200 
201  } catch (SevenZipException ex) {
202  logger.log(Level.WARNING, "Error getting archive item size and cannot detect if zipbomb. ", ex); //NON-NLS
203  return false;
204  }
205  }
206 
215  private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
216  // try to get the file type from the BB
217  String detectedFormat = null;
218  detectedFormat = archiveFile.getMIMEType();
219 
220  if (detectedFormat == null) {
221  logger.log(Level.WARNING, "Could not detect format for file: {0}", archiveFile); //NON-NLS
222 
223  // if we don't have attribute info then use file extension
224  String extension = archiveFile.getNameExtension();
225  if ("rar".equals(extension)) //NON-NLS
226  {
227  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
228  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
229  return RAR;
230  }
231 
232  // Otherwise open the archive using 7zip's built-in auto-detect functionality
233  return null;
234  } else if (detectedFormat.contains("application/x-rar-compressed")) //NON-NLS
235  {
236  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
237  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
238  return RAR;
239  }
240 
241  // Otherwise open the archive using 7zip's built-in auto-detect functionality
242  return null;
243  }
244 
253  @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."})
254  void unpack(AbstractFile archiveFile) {
255  blackboard = Case.getCurrentCase().getServices().getBlackboard();
256  String archiveFilePath;
257  try {
258  archiveFilePath = archiveFile.getUniquePath();
259  } catch (TskCoreException ex) {
260  archiveFilePath = archiveFile.getParentPath() + archiveFile.getName();
261  }
262 
263  //check if already has derived files, skip
264  try {
265  if (archiveFile.hasChildren()) {
266  //check if local unpacked dir exists
267  if (new File(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
268  logger.log(Level.INFO, "File already has been processed as it has children and local unpacked file, skipping: {0}", archiveFilePath); //NON-NLS
269  return;
270  }
271  }
272  } catch (TskCoreException e) {
273  logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", archiveFilePath); //NON-NLS
274  return;
275  }
276 
277  List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
278 
279  //recursion depth check for zip bomb
280  final long archiveId = archiveFile.getId();
281  SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = archiveDepthCountTree.findArchive(archiveId);
282  if (parentAr == null) {
283  parentAr = archiveDepthCountTree.addArchive(null, archiveId);
284  } else if (parentAr.getDepth() == MAX_DEPTH) {
285  String msg = NbBundle.getMessage(SevenZipExtractor.class,
286  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
287  String details = NbBundle.getMessage(SevenZipExtractor.class,
288  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
289  parentAr.getDepth(), archiveFilePath);
290  //MessageNotifyUtil.Notify.error(msg, details);
291  services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
292  return;
293  }
294 
295  boolean hasEncrypted = false;
296  boolean fullEncryption = true;
297 
298  ISevenZipInArchive inArchive = null;
299  SevenZipContentReadStream stream = null;
300 
301  final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
302  int processedItems = 0;
303 
304  boolean progressStarted = false;
305  try {
306  stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
307 
308  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
309  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality.
310  // All other archive formats are still opened using 7zip built-in auto-detect functionality.
311  ArchiveFormat options = get7ZipOptions(archiveFile);
312  inArchive = SevenZip.openInArchive(options, stream);
313 
314  int numItems = inArchive.getNumberOfItems();
315  logger.log(Level.INFO, "Count of items in archive: {0}: {1}", new Object[]{archiveFilePath, numItems}); //NON-NLS
316  progress.start(numItems);
317  progressStarted = true;
318 
319  final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
320 
321  //setup the archive local root folder
322  final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
323  final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
324  final File localRoot = new File(localRootAbsPath);
325  if (!localRoot.exists()) {
326  try {
327  localRoot.mkdirs();
328  } catch (SecurityException e) {
329  logger.log(Level.SEVERE, "Error setting up output path for archive root: {0}", localRootAbsPath); //NON-NLS
330  //bail
331  return;
332  }
333  }
334 
335  //initialize tree hierarchy to keep track of unpacked file structure
336  SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
337 
338  long freeDiskSpace = services.getFreeDiskSpace();
339 
340  //unpack and process every item in archive
341  int itemNumber = 0;
342  for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
343  String pathInArchive = item.getPath();
344 
345  if (pathInArchive == null || pathInArchive.isEmpty()) {
346  //some formats (.tar.gz) may not be handled correctly -- file in archive has no name/path
347  //handle this for .tar.gz and tgz but assuming the child is tar,
348  //otherwise, unpack using itemNumber as name
349 
350  //TODO this should really be signature based, not extension based
351  String archName = archiveFile.getName();
352  int dotI = archName.lastIndexOf(".");
353  String useName = null;
354  if (dotI != -1) {
355  String base = archName.substring(0, dotI);
356  String ext = archName.substring(dotI);
357  int colonIndex = ext.lastIndexOf(":");
358  if (colonIndex != -1) {
359  // If alternate data stream is found, fix the name
360  // so Windows doesn't choke on the colon character.
361  ext = ext.substring(0, colonIndex);
362  }
363  switch (ext) {
364  case ".gz": //NON-NLS
365  useName = base;
366  break;
367  case ".tgz": //NON-NLS
368  useName = base + ".tar"; //NON-NLS
369  break;
370  case ".bz2": //NON-NLS
371  useName = base;
372  break;
373  }
374  }
375 
376  if (useName == null) {
377  pathInArchive = "/" + archName + "/" + Integer.toString(itemNumber);
378  } else {
379  pathInArchive = "/" + useName;
380  }
381 
382  String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
383  archiveFilePath, pathInArchive);
384  logger.log(Level.WARNING, msg);
385 
386  }
387  archiveFilePath = FileUtil.escapeFileName(archiveFilePath);
388  ++itemNumber;
389 
390  //check if possible zip bomb
391  if (isZipBombArchiveItemCheck(archiveFile, item)) {
392  continue; //skip the item
393  }
394 
395  //find this node in the hierarchy, create if needed
396  SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive);
397 
398  String fileName = unpackedNode.getFileName();
399 
400  //update progress bar
401  progress.progress(archiveFile.getName() + ": " + fileName, processedItems);
402 
403  final boolean isEncrypted = item.isEncrypted();
404  final boolean isDir = item.isFolder();
405 
406  if (isEncrypted) {
407  logger.log(Level.WARNING, "Skipping encrypted file in archive: {0}", pathInArchive); //NON-NLS
408  hasEncrypted = true;
409  continue;
410  } else {
411  fullEncryption = false;
412  }
413 
414  // NOTE: item.getSize() may return null in case of certain
415  // archiving formats. Eg: BZ2
416  Long size = item.getSize();
417 
418  //check if unpacking this file will result in out of disk space
419  //this is additional to zip bomb prevention mechanism
420  if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size != null && size > 0) { //if free space is known and file is not empty.
421  long newDiskSpace = freeDiskSpace - size;
422  if (newDiskSpace < MIN_FREE_DISK_SPACE) {
423  String msg = NbBundle.getMessage(SevenZipExtractor.class,
424  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
425  archiveFilePath, fileName);
426  String details = NbBundle.getMessage(SevenZipExtractor.class,
427  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
428  //MessageNotifyUtil.Notify.error(msg, details);
429  services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
430  logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new Object[]{archiveFilePath, fileName}); //NON-NLS
431  logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS
432  continue; //skip this file
433  } else {
434  //update est. disk space during this archive, so we don't need to poll for every file extracted
435  freeDiskSpace = newDiskSpace;
436  }
437  }
438 
439  final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (item.getItemIndex() / 1000) + File.separator + item.getItemIndex() + "_" + new File(pathInArchive).getName());
440 
441  //final String localRelPath = unpackDir + File.separator + localFileRelPath;
442  final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
443  final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
444 
445  //create local dirs and empty files before extracted
446  File localFile = new java.io.File(localAbsPath);
447  //cannot rely on files in top-bottom order
448  if (!localFile.exists()) {
449  try {
450  if (isDir) {
451  localFile.mkdirs();
452  } else {
453  localFile.getParentFile().mkdirs();
454  try {
455  localFile.createNewFile();
456  } catch (IOException e) {
457  logger.log(Level.SEVERE, "Error creating extracted file: " + localFile.getAbsolutePath(), e); //NON-NLS
458  }
459  }
460  } catch (SecurityException e) {
461  logger.log(Level.SEVERE, "Error setting up output path for unpacked file: {0}", pathInArchive); //NON-NLS
462  //TODO consider bail out / msg to the user
463  }
464  }
465 
466  // skip the rest of this loop if we couldn't create the file
467  if (localFile.exists() == false) {
468  continue;
469  }
470 
471  final Date createTime = item.getCreationTime();
472  final Date accessTime = item.getLastAccessTime();
473  final Date writeTime = item.getLastWriteTime();
474  final long createtime = createTime == null ? 0L : createTime.getTime() / 1000;
475  final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
476  final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
477 
478  //unpack locally if a file
479  SevenZipExtractor.UnpackStream unpackStream = null;
480  if (!isDir) {
481  try {
482  if (size != null) {
483  unpackStream = new SevenZipExtractor.KnownSizeUnpackStream(localAbsPath, size);
484  } else {
485  unpackStream = new SevenZipExtractor.UnknownSizeUnpackStream(localAbsPath, freeDiskSpace);
486  }
487  item.extractSlow(unpackStream);
488  } catch (Exception e) {
489  //could be something unexpected with this file, move on
490  logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS
491  } finally {
492  if (unpackStream != null) {
493  //record derived data in unode, to be traversed later after unpacking the archive
494  unpackedNode.addDerivedInfo(unpackStream.getSize(), !isDir,
495  0L, createtime, accesstime, modtime, localRelPath);
496  unpackStream.close();
497  }
498  }
499  } else { // this is a directory, size is always 0
500  unpackedNode.addDerivedInfo(0, !isDir,
501  0L, createtime, accesstime, modtime, localRelPath);
502  }
503 
504  //update units for progress bar
505  ++processedItems;
506  }
507 
508  // add them to the DB. We wait until the end so that we have the metadata on all of the
509  // intermediate nodes since the order is not guaranteed
510  try {
511  unpackedTree.addDerivedFilesToCase();
512  unpackedFiles = unpackedTree.getAllFileObjects();
513 
514  //check if children are archives, update archive depth tracking
515  for (AbstractFile unpackedFile : unpackedFiles) {
516  if (isSevenZipExtractionSupported(unpackedFile)) {
517  archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId());
518  }
519  }
520 
521  } catch (TskCoreException e) {
522  logger.log(Level.SEVERE, "Error populating complete derived file hierarchy from the unpacked dir structure"); //NON-NLS
523  //TODO decide if anything to cleanup, for now bailing
524  }
525 
526  } catch (SevenZipException ex) {
527  logger.log(Level.WARNING, "Error unpacking file: {0}", archiveFile); //NON-NLS
528  //inbox message
529 
530  // print a message if the file is allocated
531  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
532  String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
533  archiveFile.getName());
534  String details = NbBundle.getMessage(SevenZipExtractor.class,
535  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
536  archiveFilePath, ex.getMessage());
537  services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
538  }
539  } finally {
540  if (inArchive != null) {
541  try {
542  inArchive.close();
543  } catch (SevenZipException e) {
544  logger.log(Level.SEVERE, "Error closing archive: " + archiveFile, e); //NON-NLS
545  }
546  }
547 
548  if (stream != null) {
549  try {
550  stream.close();
551  } catch (IOException ex) {
552  logger.log(Level.SEVERE, "Error closing stream after unpacking archive: " + archiveFile, ex); //NON-NLS
553  }
554  }
555 
556  //close progress bar
557  if (progressStarted) {
558  progress.finish();
559  }
560  }
561 
562  //create artifact and send user message
563  if (hasEncrypted) {
564  String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
565  try {
566  BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
567  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType));
568 
569  try {
570  // index the artifact for keyword search
571  blackboard.indexArtifact(artifact);
572  } catch (Blackboard.BlackboardException ex) {
573  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
574  MessageNotifyUtil.Notify.error(
575  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
576  }
577 
578  services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
579  } catch (TskCoreException ex) {
580  logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS
581  }
582 
583  String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
584  String details = NbBundle.getMessage(SevenZipExtractor.class,
585  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
586  archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
587  services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
588  }
589 
590  // adding unpacked extracted derived files to the job after closing relevant resources.
591  if (!unpackedFiles.isEmpty()) {
592  //currently sending a single event for all new files
593  services.fireModuleContentEvent(new ModuleContentEvent(archiveFile));
594  context.addFilesToJob(unpackedFiles);
595  }
596  }
597 
601  private abstract static class UnpackStream implements ISequentialOutStream {
602 
603  private OutputStream output;
604  private String localAbsPath;
605 
606  UnpackStream(String localAbsPath) {
607  this.localAbsPath = localAbsPath;
608  try {
609  output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
610  } catch (IOException ex) {
611  logger.log(Level.SEVERE, "Error writing extracted file: " + localAbsPath, ex); //NON-NLS
612  }
613 
614  }
615 
616  public abstract long getSize();
617 
618  OutputStream getOutput() {
619  return output;
620  }
621 
622  String getLocalAbsPath() {
623  return localAbsPath;
624  }
625 
626  public void close() {
627  if (output != null) {
628  try {
629  output.flush();
630  output.close();
631  } catch (IOException e) {
632  logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
633  }
634  }
635  }
636  }
637 
641  private static class UnknownSizeUnpackStream extends UnpackStream {
642 
643  private long freeDiskSpace;
644  private boolean outOfSpace = false;
645  private long bytesWritten = 0;
646 
647  UnknownSizeUnpackStream(String localAbsPath, long freeDiskSpace) {
648  super(localAbsPath);
649  this.freeDiskSpace = freeDiskSpace;
650  }
651 
652  @Override
653  public long getSize() {
654  return this.bytesWritten;
655  }
656 
657  @Override
658  public int write(byte[] bytes) throws SevenZipException {
659  try {
660  // If the content size is unknown, cautiously write to disk.
661  // Write only if byte array is less than 80% of the current
662  // free disk space.
663  if (freeDiskSpace == IngestMonitor.DISK_FREE_SPACE_UNKNOWN || bytes.length < 0.8 * freeDiskSpace) {
664  getOutput().write(bytes);
665  // NOTE: this method is called multiple times for a
666  // single extractSlow() call. Update bytesWritten and
667  // freeDiskSpace after every write operation.
668  this.bytesWritten += bytes.length;
669  this.freeDiskSpace -= bytes.length;
670  } else {
671  this.outOfSpace = true;
672  logger.log(Level.INFO, NbBundle.getMessage(
673  SevenZipExtractor.class,
674  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
675  throw new SevenZipException(
676  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
677  }
678  } catch (IOException ex) {
679  throw new SevenZipException(
680  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
681  getLocalAbsPath()), ex);
682  }
683  return bytes.length;
684  }
685 
686  @Override
687  public void close() {
688  if (getOutput() != null) {
689  try {
690  getOutput().flush();
691  getOutput().close();
692  if (this.outOfSpace) {
693  Files.delete(Paths.get(getLocalAbsPath()));
694  }
695  } catch (IOException e) {
696  logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", getLocalAbsPath()); //NON-NLS
697  }
698  }
699  }
700  }
701 
705  private static class KnownSizeUnpackStream extends UnpackStream {
706 
707  private long size;
708 
709  KnownSizeUnpackStream(String localAbsPath, long size) {
710  super(localAbsPath);
711  this.size = size;
712  }
713 
714  @Override
715  public long getSize() {
716  return this.size;
717  }
718 
719  @Override
720  public int write(byte[] bytes) throws SevenZipException {
721  try {
722  getOutput().write(bytes);
723  } catch (IOException ex) {
724  throw new SevenZipException(
725  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
726  getLocalAbsPath()), ex);
727  }
728  return bytes.length;
729  }
730  }
731 
739  private class UnpackedTree {
740 
741  final UnpackedNode rootNode;
742 
750  UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
751  this.rootNode = new UnpackedNode();
752  this.rootNode.setFile(archiveFile);
753  this.rootNode.setFileName(archiveFile.getName());
754  this.rootNode.localRelPath = localPathRoot;
755  }
756 
766  UnpackedNode addNode(String filePath) {
767  String[] toks = filePath.split("[\\/\\\\]");
768  List<String> tokens = new ArrayList<>();
769  for (int i = 0; i < toks.length; ++i) {
770  if (!toks[i].isEmpty()) {
771  tokens.add(toks[i]);
772  }
773  }
774  return addNode(rootNode, tokens);
775  }
776 
785  private UnpackedNode addNode(UnpackedNode parent, List<String> tokenPath) {
786  // we found all of the tokens
787  if (tokenPath.isEmpty()) {
788  return parent;
789  }
790 
791  // get the next name in the path and look it up
792  String childName = tokenPath.remove(0);
793  UnpackedNode child = parent.getChild(childName);
794  // create new node
795  if (child == null) {
796  child = new UnpackedNode(childName, parent);
797  }
798 
799  // go down one more level
800  return addNode(child, tokenPath);
801  }
802 
809  List<AbstractFile> getRootFileObjects() {
810  List<AbstractFile> ret = new ArrayList<>();
811  for (UnpackedNode child : rootNode.children) {
812  ret.add(child.getFile());
813  }
814  return ret;
815  }
816 
823  List<AbstractFile> getAllFileObjects() {
824  List<AbstractFile> ret = new ArrayList<>();
825  for (UnpackedNode child : rootNode.children) {
826  getAllFileObjectsRec(ret, child);
827  }
828  return ret;
829  }
830 
831  private void getAllFileObjectsRec(List<AbstractFile> list, UnpackedNode parent) {
832  list.add(parent.getFile());
833  for (UnpackedNode child : parent.children) {
834  getAllFileObjectsRec(list, child);
835  }
836  }
837 
842  void addDerivedFilesToCase() throws TskCoreException {
843  final FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
844  for (UnpackedNode child : rootNode.children) {
845  addDerivedFilesToCaseRec(child, fileManager);
846  }
847  }
848 
849  private void addDerivedFilesToCaseRec(UnpackedNode node, FileManager fileManager) throws TskCoreException {
850  final String fileName = node.getFileName();
851 
852  try {
853  DerivedFile df = fileManager.addDerivedFile(fileName, node.getLocalRelPath(), node.getSize(),
854  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
855  node.isIsFile(), node.getParent().getFile(), "", EmbeddedFileExtractorModuleFactory.getModuleName(),
856  "", "", TskData.EncodingType.XOR1);
857  node.setFile(df);
858 
859  } catch (TskCoreException ex) {
860  logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS
861  throw new TskCoreException(
862  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
863  fileName), ex);
864  }
865 
866  //recurse
867  for (UnpackedNode child : node.children) {
868  addDerivedFilesToCaseRec(child, fileManager);
869  }
870  }
871 
875  private class UnpackedNode {
876 
877  private String fileName;
878  private AbstractFile file;
879  private List<UnpackedNode> children = new ArrayList<>();
880  private String localRelPath = "";
881  private long size;
882  private long ctime, crtime, atime, mtime;
883  private boolean isFile;
885 
886  //root constructor
887  UnpackedNode() {
888  }
889 
890  //child node constructor
891  UnpackedNode(String fileName, UnpackedNode parent) {
892  this.fileName = fileName;
893  this.parent = parent;
894  this.localRelPath = parent.localRelPath + File.separator + fileName;
895  //new child derived file will be set by unpack() method
896  parent.children.add(this);
897 
898  }
899 
900  public long getCtime() {
901  return ctime;
902  }
903 
904  public long getCrtime() {
905  return crtime;
906  }
907 
908  public long getAtime() {
909  return atime;
910  }
911 
912  public long getMtime() {
913  return mtime;
914  }
915 
916  public void setFileName(String fileName) {
917  this.fileName = fileName;
918  }
919 
920  UnpackedNode getParent() {
921  return parent;
922  }
923 
924  void addDerivedInfo(long size,
925  boolean isFile,
926  long ctime, long crtime, long atime, long mtime, String relLocalPath) {
927  this.size = size;
928  this.isFile = isFile;
929  this.ctime = ctime;
930  this.crtime = crtime;
931  this.atime = atime;
932  this.mtime = mtime;
933  this.localRelPath = relLocalPath;
934  }
935 
936  void setFile(AbstractFile file) {
937  this.file = file;
938  }
939 
947  UnpackedNode getChild(String childFileName) {
948  UnpackedNode ret = null;
949  for (UnpackedNode child : children) {
950  if (child.fileName.equals(childFileName)) {
951  ret = child;
952  break;
953  }
954  }
955  return ret;
956  }
957 
958  public String getFileName() {
959  return fileName;
960  }
961 
962  public AbstractFile getFile() {
963  return file;
964  }
965 
966  public String getLocalRelPath() {
967  return localRelPath;
968  }
969 
970  public long getSize() {
971  return size;
972  }
973 
974  public boolean isIsFile() {
975  return isFile;
976  }
977  }
978  }
979 
983  private static class ArchiveDepthCountTree {
984 
985  //keeps all nodes refs for easy search
986  private final List<Archive> archives = new ArrayList<>();
987 
995  Archive findArchive(long objectId) {
996  for (Archive ar : archives) {
997  if (ar.objectId == objectId) {
998  return ar;
999  }
1000  }
1001 
1002  return null;
1003  }
1004 
1013  Archive addArchive(Archive parent, long objectId) {
1014  Archive child = new Archive(parent, objectId);
1015  archives.add(child);
1016  return child;
1017  }
1018 
1019  private static class Archive {
1020 
1021  int depth;
1022  long objectId;
1023  Archive parent;
1024  List<Archive> children;
1025 
1026  Archive(Archive parent, long objectId) {
1027  this.parent = parent;
1028  this.objectId = objectId;
1029  children = new ArrayList<>();
1030  if (parent != null) {
1031  parent.children.add(this);
1032  this.depth = parent.depth + 1;
1033  } else {
1034  this.depth = 0;
1035  }
1036  }
1037 
1043  int getDepth() {
1044  return depth;
1045  }
1046  }
1047  }
1048 
1049 }
synchronized DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, Content parentObj, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType)
UnpackedNode addNode(UnpackedNode parent, List< String > tokenPath)

Copyright © 2012-2016 Basis Technology. Generated on: Tue Feb 20 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.