19 package org.sleuthkit.autopsy.modules.embeddedfileextractor;
21 import java.io.BufferedOutputStream;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
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.netbeans.api.progress.ProgressHandleFactory;
43 import org.openide.util.Exceptions;
44 import org.openide.util.NbBundle;
64 class SevenZipExtractor {
66 private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
67 private IngestServices services = IngestServices.getInstance();
68 private final IngestJobContext context;
69 private final FileTypeDetector fileTypeDetector;
70 static final String[] SUPPORTED_EXTENSIONS = {
"zip",
"rar",
"arj",
"7z",
"7zip",
"gzip",
"gz",
"bzip2",
"tar",
"tgz",};
72 private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
73 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
74 private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
75 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
77 private static final int MAX_DEPTH = 4;
78 private static final int MAX_COMPRESSION_RATIO = 600;
79 private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
80 private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L;
82 private ArchiveDepthCountTree archiveDepthCountTree;
84 private String moduleDirRelative;
85 private String moduleDirAbsolute;
87 private String getLocalRootAbsPath(String uniqueArchiveFileName) {
88 return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
94 ZIP(
"application/zip"),
95 SEVENZ(
"application/x-7z-compressed"),
104 this.mimeType = mimeType;
109 return this.mimeType;
115 if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
117 SevenZip.initSevenZipFromPlatformJAR();
118 String platform = SevenZip.getUsedPlatform();
119 logger.log(Level.INFO,
"7-Zip-JBinding library was initialized on supported platform: {0}", platform);
120 }
catch (SevenZipNativeInitializationException e) {
121 logger.log(Level.SEVERE,
"Error initializing 7-Zip-JBinding library", e);
122 String msg = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg",
123 EmbeddedFileExtractorModuleFactory.getModuleName());
124 String details = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
126 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
127 throw new IngestModuleException(e.getMessage());
130 this.context = context;
131 this.fileTypeDetector = fileTypeDetector;
132 this.moduleDirRelative = moduleDirRelative;
133 this.moduleDirAbsolute = moduleDirAbsolute;
134 this.archiveDepthCountTree =
new ArchiveDepthCountTree();
146 boolean isSevenZipExtractionSupported(AbstractFile abstractFile) {
148 String abstractFileMimeType = fileTypeDetector.getFileType(abstractFile);
149 for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) {
150 if (s.toString().equals(abstractFileMimeType)) {
156 }
catch (TskCoreException ex) {
157 logger.log(Level.WARNING,
"Error executing FileTypeDetector.getFileType()", ex);
161 final String extension = abstractFile.getNameExtension();
162 for (String supportedExtension : SUPPORTED_EXTENSIONS) {
163 if (extension.equals(supportedExtension)) {
182 private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) {
184 final Long archiveItemSize = archiveFileItem.getSize();
187 if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
191 final Long archiveItemPackedSize = archiveFileItem.getPackedSize();
193 if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
194 logger.log(Level.WARNING,
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}",
new Object[]{archiveFile.getName(), archiveFileItem.getPath()});
198 int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
200 if (cRatio >= MAX_COMPRESSION_RATIO) {
201 String itemName = archiveFileItem.getPath();
202 logger.log(Level.INFO,
"Possible zip bomb detected, compression ration: {0} for in archive item: {1}",
new Object[]{cRatio, itemName});
203 String msg = NbBundle.getMessage(this.getClass(),
204 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
207 path = archiveFile.getUniquePath();
208 }
catch (TskCoreException ex) {
209 path = archiveFile.getParentPath() + archiveFile.getName();
211 String details = NbBundle.getMessage(this.getClass(),
212 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
214 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
220 }
catch (SevenZipException ex) {
221 logger.log(Level.SEVERE,
"Error getting archive item size and cannot detect if zipbomb. ", ex);
233 private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
235 String detectedFormat = null;
237 ArrayList<BlackboardAttribute> attributes = archiveFile.getGenInfoAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG);
238 for (BlackboardAttribute attribute : attributes) {
239 detectedFormat = attribute.getValueString();
242 }
catch (TskCoreException ex) {
243 logger.log(Level.WARNING,
"Couldn't obtain file attributes for file: " + archiveFile.toString(), ex);
246 if (detectedFormat == null) {
247 logger.log(Level.WARNING,
"Could not detect format for file: " + archiveFile);
250 String extension = archiveFile.getNameExtension();
251 if (
"rar".equals(extension))
260 }
else if (detectedFormat.contains(
"application/x-rar-compressed"))
278 void unpack(AbstractFile archiveFile) {
279 String archiveFilePath;
281 archiveFilePath = archiveFile.getUniquePath();
282 }
catch (TskCoreException ex) {
283 archiveFilePath = archiveFile.getParentPath() + archiveFile.getName();
288 if (archiveFile.hasChildren()) {
290 if (
new File(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
291 logger.log(Level.INFO,
"File already has been processed as it has children and local unpacked file, skipping: {0}", archiveFilePath);
295 }
catch (TskCoreException e) {
296 logger.log(Level.INFO,
"Error checking if file already has been processed, skipping: {0}", archiveFilePath);
301 List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
304 final long archiveId = archiveFile.getId();
305 SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = archiveDepthCountTree.findArchive(archiveId);
306 if (parentAr == null) {
307 parentAr = archiveDepthCountTree.addArchive(null, archiveId);
308 }
else if (parentAr.getDepth() == MAX_DEPTH) {
309 String msg = NbBundle.getMessage(this.getClass(),
310 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
311 String details = NbBundle.getMessage(this.getClass(),
312 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
313 parentAr.getDepth(), archiveFilePath);
315 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
319 boolean hasEncrypted =
false;
320 boolean fullEncryption =
true;
322 ISevenZipInArchive inArchive = null;
323 SevenZipContentReadStream stream = null;
325 final ProgressHandle progress = ProgressHandleFactory.createHandle(
326 NbBundle.getMessage(
this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
327 int processedItems = 0;
329 boolean progressStarted =
false;
331 stream =
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
336 ArchiveFormat options = get7ZipOptions(archiveFile);
337 inArchive = SevenZip.openInArchive(options, stream);
339 int numItems = inArchive.getNumberOfItems();
340 logger.log(Level.INFO,
"Count of items in archive: {0}: {1}",
new Object[]{archiveFilePath, numItems});
341 progress.start(numItems);
342 progressStarted =
true;
344 final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
347 final String uniqueArchiveFileName = EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile);
348 final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
349 final File localRoot =
new File(localRootAbsPath);
350 if (!localRoot.exists()) {
353 }
catch (SecurityException e) {
354 logger.log(Level.SEVERE,
"Error setting up output path for archive root: {0}", localRootAbsPath);
361 SevenZipExtractor.UnpackedTree unpackedTree =
new SevenZipExtractor.UnpackedTree(moduleDirRelative +
"/" + uniqueArchiveFileName, archiveFile);
363 long freeDiskSpace = services.getFreeDiskSpace();
367 for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
368 String pathInArchive = item.getPath();
370 if (pathInArchive == null || pathInArchive.isEmpty()) {
376 String archName = archiveFile.getName();
377 int dotI = archName.lastIndexOf(
".");
378 String useName = null;
380 String base = archName.substring(0, dotI);
381 String ext = archName.substring(dotI);
387 useName = base +
".tar";
392 if (useName == null) {
393 pathInArchive =
"/" + archName +
"/" + Integer.toString(itemNumber);
395 pathInArchive =
"/" + useName;
398 String msg = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
399 archiveFilePath, pathInArchive);
400 logger.log(Level.WARNING, msg);
404 logger.log(Level.INFO,
"Extracted item path: {0}", pathInArchive);
407 if (isZipBombArchiveItemCheck(archiveFile, item)) {
412 SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive);
414 String fileName = unpackedNode.getFileName();
417 progress.progress(archiveFile.getName() +
": " + fileName, processedItems);
419 final boolean isEncrypted = item.isEncrypted();
420 final boolean isDir = item.isFolder();
423 logger.log(Level.WARNING,
"Skipping encrypted file in archive: {0}", pathInArchive);
427 fullEncryption =
false;
430 final Long size = item.getSize();
435 logger.log(Level.WARNING,
"Size cannot be determined. Skipping file in archive: {0}", pathInArchive);
441 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size > 0) {
442 long newDiskSpace = freeDiskSpace - size;
443 if (newDiskSpace < MIN_FREE_DISK_SPACE) {
444 String msg = NbBundle.getMessage(this.getClass(),
445 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
446 archiveFilePath, fileName);
447 String details = NbBundle.getMessage(this.getClass(),
448 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
450 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
451 logger.log(Level.INFO,
"Skipping archive item due to insufficient disk space: {0}, {1}",
new Object[]{archiveFilePath, fileName});
452 logger.log(Level.INFO,
"Available disk space: {0}",
new Object[]{freeDiskSpace});
456 freeDiskSpace = newDiskSpace;
460 final String uniqueExtractedName = uniqueArchiveFileName + File.separator + (item.getItemIndex() / 1000) + File.separator + item.getItemIndex() +
new File(pathInArchive).getName();
463 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
464 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
467 File localFile =
new java.io.File(localAbsPath);
469 if (!localFile.exists()) {
474 localFile.getParentFile().mkdirs();
476 localFile.createNewFile();
477 }
catch (IOException ex) {
478 logger.log(Level.SEVERE,
"Error creating extracted file: " + localFile.getAbsolutePath(), ex);
481 }
catch (SecurityException e) {
482 logger.log(Level.SEVERE,
"Error setting up output path for unpacked file: {0}", pathInArchive);
488 if (localFile.exists() ==
false) {
492 final Date createTime = item.getCreationTime();
493 final Date accessTime = item.getLastAccessTime();
494 final Date writeTime = item.getLastWriteTime();
495 final long createtime = createTime == null ? 0L : createTime.getTime() / 1000;
496 final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
497 final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
500 unpackedNode.addDerivedInfo(size, !isDir,
501 0L, createtime, accesstime, modtime, localRelPath);
505 SevenZipExtractor.UnpackStream unpackStream = null;
507 unpackStream =
new SevenZipExtractor.UnpackStream(localAbsPath);
508 item.extractSlow(unpackStream);
509 }
catch (Exception e) {
511 logger.log(Level.WARNING,
"Could not extract file from archive: " + localAbsPath, e);
513 if (unpackStream != null) {
514 unpackStream.close();
526 unpackedTree.addDerivedFilesToCase();
527 unpackedFiles = unpackedTree.getAllFileObjects();
530 for (AbstractFile unpackedFile : unpackedFiles) {
531 if (isSevenZipExtractionSupported(unpackedFile)) {
532 archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId());
536 }
catch (TskCoreException e) {
537 logger.log(Level.SEVERE,
"Error populating complete derived file hierarchy from the unpacked dir structure");
541 }
catch (SevenZipException ex) {
542 logger.log(Level.SEVERE,
"Error unpacking file: " + archiveFile, ex);
546 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
547 String msg = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
548 archiveFile.getName());
549 String details = NbBundle.getMessage(this.getClass(),
550 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
551 archiveFilePath, ex.getMessage());
552 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
555 if (inArchive != null) {
558 }
catch (SevenZipException e) {
559 logger.log(Level.SEVERE,
"Error closing archive: " + archiveFile, e);
563 if (stream != null) {
566 }
catch (IOException ex) {
567 logger.log(Level.SEVERE,
"Error closing stream after unpacking archive: " + archiveFile, ex);
572 if (progressStarted) {
579 String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
581 BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
582 artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType));
583 services.fireModuleDataEvent(
new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
584 }
catch (TskCoreException ex) {
585 logger.log(Level.SEVERE,
"Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex);
588 String msg = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
589 String details = NbBundle.getMessage(this.getClass(),
590 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
591 archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
592 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
596 if (!unpackedFiles.isEmpty()) {
598 services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
599 context.addFilesToJob(unpackedFiles);
613 output =
new BufferedOutputStream(
new FileOutputStream(localAbsPath));
614 }
catch (FileNotFoundException ex) {
615 logger.log(Level.SEVERE,
"Error writing extracted file: " + localAbsPath, ex);
621 public int write(byte[] bytes)
throws SevenZipException {
624 }
catch (IOException ex) {
625 throw new SevenZipException(
626 NbBundle.getMessage(
this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
633 if (output != null) {
637 }
catch (IOException e) {
638 logger.log(Level.SEVERE,
"Error closing unpack stream for file: {0}", localAbsPath);
664 this.rootNode.setFile(archiveFile);
678 String[] toks = filePath.split(
"[\\/\\\\]");
679 List<String> tokens =
new ArrayList<>();
680 for (
int i = 0; i < toks.length; ++i) {
681 if (!toks[i].isEmpty()) {
685 return addNode(rootNode, tokens);
696 if (tokenPath.isEmpty()) {
701 String childName = tokenPath.remove(0);
709 return addNode(child, tokenPath);
718 List<AbstractFile> getRootFileObjects() {
719 List<AbstractFile> ret =
new ArrayList<>();
720 for (UnpackedNode child : rootNode.
children) {
721 ret.add(child.getFile());
732 List<AbstractFile> getAllFileObjects() {
733 List<AbstractFile> ret =
new ArrayList<>();
734 for (UnpackedNode child : rootNode.
children) {
753 for (UnpackedNode child : rootNode.
children) {
759 final String fileName = node.getFileName();
763 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
767 }
catch (TskCoreException ex) {
768 logger.log(Level.SEVERE,
"Error adding a derived file to db:" + fileName, ex);
769 throw new TskCoreException(
770 NbBundle.getMessage(
this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
787 private List<UnpackedNode>
children =
new ArrayList<>();
788 private String localRelPath =
"";
790 private long ctime, crtime, atime, mtime;
832 void addDerivedInfo(
long size,
834 long ctime,
long crtime,
long atime,
long mtime, String relLocalPath) {
838 this.crtime = crtime;
841 this.localRelPath = relLocalPath;
854 UnpackedNode getChild(String childFileName) {
855 UnpackedNode ret = null;
856 for (UnpackedNode child : children) {
857 if (child.fileName.equals(childFileName)) {
893 private final List<Archive>
archives =
new ArrayList<>();
901 Archive findArchive(
long objectId) {
903 if (ar.objectId == objectId) {
918 Archive addArchive(Archive parent,
long objectId) {
919 Archive child =
new Archive(parent, objectId);
929 List<Archive> children;
932 this.parent = parent;
933 this.objectId = objectId;
934 children =
new ArrayList<>();
935 if (parent != null) {
936 parent.children.add(
this);
937 this.depth = parent.depth + 1;
FileManager getFileManager()
synchronized DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, AbstractFile parentFile, String rederiveDetails, String toolName, String toolVersion, String otherDetails)
static Case getCurrentCase()