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.nio.file.Files;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.List;
33 import java.util.logging.Level;
34 import net.sf.sevenzipjbinding.ArchiveFormat;
35 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
36 import net.sf.sevenzipjbinding.ISequentialOutStream;
37 import net.sf.sevenzipjbinding.ISevenZipInArchive;
38 import net.sf.sevenzipjbinding.SevenZip;
39 import net.sf.sevenzipjbinding.SevenZipException;
40 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
41 import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
42 import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
43 import org.netbeans.api.progress.ProgressHandle;
44 import org.netbeans.api.progress.ProgressHandleFactory;
45 import org.openide.util.NbBundle;
68 class SevenZipExtractor {
70 private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
71 private IngestServices services = IngestServices.getInstance();
72 private final IngestJobContext context;
73 private final FileTypeDetector fileTypeDetector;
74 static final String[] SUPPORTED_EXTENSIONS = {
"zip",
"rar",
"arj",
"7z",
"7zip",
"gzip",
"gz",
"bzip2",
"tar",
"tgz",};
76 private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
77 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
78 private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
79 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
81 private static final int MAX_DEPTH = 4;
82 private static final int MAX_COMPRESSION_RATIO = 600;
83 private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
84 private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L;
86 private ArchiveDepthCountTree archiveDepthCountTree;
88 private String moduleDirRelative;
89 private String moduleDirAbsolute;
91 private Blackboard blackboard;
93 private String getLocalRootAbsPath(String uniqueArchiveFileName) {
94 return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
113 this.mimeType = mimeType;
118 return this.mimeType;
124 if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
126 SevenZip.initSevenZipFromPlatformJAR();
127 String platform = SevenZip.getUsedPlatform();
128 logger.log(Level.INFO,
"7-Zip-JBinding library was initialized on supported platform: {0}", platform);
129 }
catch (SevenZipNativeInitializationException e) {
130 logger.log(Level.SEVERE,
"Error initializing 7-Zip-JBinding library", e);
131 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg",
132 EmbeddedFileExtractorModuleFactory.getModuleName());
133 String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
135 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
136 throw new IngestModuleException(e.getMessage(), e);
139 this.context = context;
140 this.fileTypeDetector = fileTypeDetector;
141 this.moduleDirRelative = moduleDirRelative;
142 this.moduleDirAbsolute = moduleDirAbsolute;
143 this.archiveDepthCountTree =
new ArchiveDepthCountTree();
156 boolean isSevenZipExtractionSupported(AbstractFile abstractFile) {
158 String abstractFileMimeType = fileTypeDetector.getFileType(abstractFile);
159 for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) {
160 if (s.toString().equals(abstractFileMimeType)) {
166 }
catch (TskCoreException ex) {
167 logger.log(Level.WARNING,
"Error executing FileTypeDetector.getFileType()", ex);
171 final String extension = abstractFile.getNameExtension();
172 for (String supportedExtension : SUPPORTED_EXTENSIONS) {
173 if (extension.equals(supportedExtension)) {
193 private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) {
195 final Long archiveItemSize = archiveFileItem.getSize();
198 if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
202 final Long archiveItemPackedSize = archiveFileItem.getPackedSize();
204 if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
205 logger.log(Level.WARNING,
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}",
new Object[]{archiveFile.getName(), archiveFileItem.getPath()});
209 int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
211 if (cRatio >= MAX_COMPRESSION_RATIO) {
212 String itemName = archiveFileItem.getPath();
213 logger.log(Level.INFO,
"Possible zip bomb detected, compression ration: {0} for in archive item: {1}",
new Object[]{cRatio, itemName});
214 String msg = NbBundle.getMessage(SevenZipExtractor.class,
215 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
218 path = archiveFile.getUniquePath();
219 }
catch (TskCoreException ex) {
220 path = archiveFile.getParentPath() + archiveFile.getName();
222 String details = NbBundle.getMessage(SevenZipExtractor.class,
223 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
225 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
231 }
catch (SevenZipException ex) {
232 logger.log(Level.WARNING,
"Error getting archive item size and cannot detect if zipbomb. ", ex);
245 private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
247 String detectedFormat = null;
248 detectedFormat = archiveFile.getMIMEType();
250 if (detectedFormat == null) {
251 logger.log(Level.WARNING,
"Could not detect format for file: {0}", archiveFile);
254 String extension = archiveFile.getNameExtension();
255 if (
"rar".equals(extension))
264 }
else if (detectedFormat.contains(
"application/x-rar-compressed"))
283 void unpack(AbstractFile archiveFile) {
284 blackboard = Case.getCurrentCase().getServices().getBlackboard();
285 String archiveFilePath;
287 archiveFilePath = archiveFile.getUniquePath();
288 }
catch (TskCoreException ex) {
289 archiveFilePath = archiveFile.getParentPath() + archiveFile.getName();
294 if (archiveFile.hasChildren()) {
296 if (
new File(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
297 logger.log(Level.INFO,
"File already has been processed as it has children and local unpacked file, skipping: {0}", archiveFilePath);
301 }
catch (TskCoreException e) {
302 logger.log(Level.INFO,
"Error checking if file already has been processed, skipping: {0}", archiveFilePath);
306 List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
309 final long archiveId = archiveFile.getId();
310 SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = archiveDepthCountTree.findArchive(archiveId);
311 if (parentAr == null) {
312 parentAr = archiveDepthCountTree.addArchive(null, archiveId);
313 }
else if (parentAr.getDepth() == MAX_DEPTH) {
314 String msg = NbBundle.getMessage(SevenZipExtractor.class,
315 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
316 String details = NbBundle.getMessage(SevenZipExtractor.class,
317 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
318 parentAr.getDepth(), archiveFilePath);
320 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
324 boolean hasEncrypted =
false;
325 boolean fullEncryption =
true;
327 ISevenZipInArchive inArchive = null;
328 SevenZipContentReadStream stream = null;
330 final ProgressHandle progress = ProgressHandleFactory.createHandle(
331 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
332 int processedItems = 0;
334 boolean progressStarted =
false;
336 stream =
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
341 ArchiveFormat options = get7ZipOptions(archiveFile);
342 inArchive = SevenZip.openInArchive(options, stream);
344 int numItems = inArchive.getNumberOfItems();
345 logger.log(Level.INFO,
"Count of items in archive: {0}: {1}",
new Object[]{archiveFilePath, numItems});
346 progress.start(numItems);
347 progressStarted =
true;
349 final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
352 final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
353 final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
354 final File localRoot =
new File(localRootAbsPath);
355 if (!localRoot.exists()) {
358 }
catch (SecurityException e) {
359 logger.log(Level.SEVERE,
"Error setting up output path for archive root: {0}", localRootAbsPath);
366 SevenZipExtractor.UnpackedTree unpackedTree =
new SevenZipExtractor.UnpackedTree(moduleDirRelative +
"/" + uniqueArchiveFileName, archiveFile);
368 long freeDiskSpace = services.getFreeDiskSpace();
372 for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
373 String pathInArchive = item.getPath();
375 if (pathInArchive == null || pathInArchive.isEmpty()) {
381 String archName = archiveFile.getName();
382 int dotI = archName.lastIndexOf(
".");
383 String useName = null;
385 String base = archName.substring(0, dotI);
386 String ext = archName.substring(dotI);
387 int colonIndex = ext.lastIndexOf(
":");
388 if (colonIndex != -1) {
391 ext = ext.substring(0, colonIndex);
398 useName = base +
".tar";
406 if (useName == null) {
407 pathInArchive =
"/" + archName +
"/" + Integer.toString(itemNumber);
409 pathInArchive =
"/" + useName;
412 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
413 archiveFilePath, pathInArchive);
414 logger.log(Level.WARNING, msg);
417 archiveFilePath = FileUtil.escapeFileName(archiveFilePath);
421 if (isZipBombArchiveItemCheck(archiveFile, item)) {
426 SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive);
428 String fileName = unpackedNode.getFileName();
431 progress.progress(archiveFile.getName() +
": " + fileName, processedItems);
433 final boolean isEncrypted = item.isEncrypted();
434 final boolean isDir = item.isFolder();
437 logger.log(Level.WARNING,
"Skipping encrypted file in archive: {0}", pathInArchive);
441 fullEncryption =
false;
446 Long size = item.getSize();
450 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size != null && size > 0) {
451 long newDiskSpace = freeDiskSpace - size;
452 if (newDiskSpace < MIN_FREE_DISK_SPACE) {
453 String msg = NbBundle.getMessage(SevenZipExtractor.class,
454 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
455 archiveFilePath, fileName);
456 String details = NbBundle.getMessage(SevenZipExtractor.class,
457 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
459 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
460 logger.log(Level.INFO,
"Skipping archive item due to insufficient disk space: {0}, {1}",
new Object[]{archiveFilePath, fileName});
461 logger.log(Level.INFO,
"Available disk space: {0}",
new Object[]{freeDiskSpace});
465 freeDiskSpace = newDiskSpace;
469 final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (item.getItemIndex() / 1000) + File.separator + item.getItemIndex() +
"_" +
new File(pathInArchive).getName());
472 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
473 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
476 File localFile =
new java.io.File(localAbsPath);
478 if (!localFile.exists()) {
483 localFile.getParentFile().mkdirs();
485 localFile.createNewFile();
486 }
catch (IOException e) {
487 logger.log(Level.SEVERE,
"Error creating extracted file: " + localFile.getAbsolutePath(), e);
490 }
catch (SecurityException e) {
491 logger.log(Level.SEVERE,
"Error setting up output path for unpacked file: {0}", pathInArchive);
497 if (localFile.exists() ==
false) {
501 final Date createTime = item.getCreationTime();
502 final Date accessTime = item.getLastAccessTime();
503 final Date writeTime = item.getLastWriteTime();
504 final long createtime = createTime == null ? 0L : createTime.getTime() / 1000;
505 final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
506 final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
509 SevenZipExtractor.UnpackStream unpackStream = null;
513 unpackStream =
new SevenZipExtractor.KnownSizeUnpackStream(localAbsPath, size);
515 unpackStream =
new SevenZipExtractor.UnknownSizeUnpackStream(localAbsPath, freeDiskSpace);
517 item.extractSlow(unpackStream);
518 }
catch (Exception e) {
520 logger.log(Level.WARNING,
"Could not extract file from archive: " + localAbsPath, e);
522 if (unpackStream != null) {
524 unpackedNode.addDerivedInfo(unpackStream.getSize(), !isDir,
525 0L, createtime, accesstime, modtime, localRelPath);
526 unpackStream.close();
530 unpackedNode.addDerivedInfo(0, !isDir,
531 0L, createtime, accesstime, modtime, localRelPath);
541 unpackedTree.addDerivedFilesToCase();
542 unpackedFiles = unpackedTree.getAllFileObjects();
545 for (AbstractFile unpackedFile : unpackedFiles) {
546 if (isSevenZipExtractionSupported(unpackedFile)) {
547 archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId());
551 }
catch (TskCoreException e) {
552 logger.log(Level.SEVERE,
"Error populating complete derived file hierarchy from the unpacked dir structure");
556 }
catch (SevenZipException ex) {
557 logger.log(Level.WARNING,
"Error unpacking file: {0}", archiveFile);
561 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
562 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
563 archiveFile.getName());
564 String details = NbBundle.getMessage(SevenZipExtractor.class,
565 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
566 archiveFilePath, ex.getMessage());
567 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
570 if (inArchive != null) {
573 }
catch (SevenZipException e) {
574 logger.log(Level.SEVERE,
"Error closing archive: " + archiveFile, e);
578 if (stream != null) {
581 }
catch (IOException ex) {
582 logger.log(Level.SEVERE,
"Error closing stream after unpacking archive: " + archiveFile, ex);
587 if (progressStarted) {
594 String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
596 BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
597 artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType));
601 blackboard.indexArtifact(artifact);
602 }
catch (Blackboard.BlackboardException ex) {
603 logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class,
"Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex);
604 MessageNotifyUtil.Notify.error(
605 NbBundle.getMessage(Blackboard.class,
"Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName());
608 services.fireModuleDataEvent(
new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
609 }
catch (TskCoreException ex) {
610 logger.log(Level.SEVERE,
"Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex);
613 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
614 String details = NbBundle.getMessage(SevenZipExtractor.class,
615 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
616 archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
617 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
621 if (!unpackedFiles.isEmpty()) {
623 services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
624 context.addFilesToJob(unpackedFiles);
631 private abstract static class UnpackStream implements ISequentialOutStream {
639 output =
new BufferedOutputStream(
new FileOutputStream(localAbsPath));
640 }
catch (FileNotFoundException ex) {
641 logger.log(Level.SEVERE,
"Error writing extracted file: " + localAbsPath, ex);
646 public abstract long getSize();
648 OutputStream getOutput() {
652 String getLocalAbsPath() {
657 if (output != null) {
661 }
catch (IOException e) {
662 logger.log(Level.SEVERE,
"Error closing unpack stream for file: {0}", localAbsPath);
688 public int write(byte[] bytes)
throws SevenZipException {
694 getOutput().write(bytes);
698 this.bytesWritten += bytes.length;
699 this.freeDiskSpace -= bytes.length;
701 this.outOfSpace =
true;
702 logger.log(Level.INFO, NbBundle.getMessage(
703 SevenZipExtractor.class,
704 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
705 throw new SevenZipException(
706 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
708 }
catch (IOException ex) {
709 throw new SevenZipException(
710 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
711 getLocalAbsPath()), ex);
718 if (getOutput() != null) {
722 if (this.outOfSpace) {
723 Files.delete(Paths.get(getLocalAbsPath()));
725 }
catch (IOException e) {
726 logger.log(Level.SEVERE,
"Error closing unpack stream for file: {0}", getLocalAbsPath());
750 public int write(byte[] bytes)
throws SevenZipException {
752 getOutput().write(bytes);
753 }
catch (IOException ex) {
754 throw new SevenZipException(
755 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
756 getLocalAbsPath()), ex);
780 UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
782 this.rootNode.setFile(archiveFile);
797 String[] toks = filePath.split(
"[\\/\\\\]");
798 List<String> tokens =
new ArrayList<>();
799 for (
int i = 0; i < toks.length; ++i) {
800 if (!toks[i].isEmpty()) {
804 return addNode(rootNode, tokens);
816 if (tokenPath.isEmpty()) {
821 String childName = tokenPath.remove(0);
829 return addNode(child, tokenPath);
838 List<AbstractFile> getRootFileObjects() {
839 List<AbstractFile> ret =
new ArrayList<>();
840 for (UnpackedNode child : rootNode.
children) {
841 ret.add(child.getFile());
852 List<AbstractFile> getAllFileObjects() {
853 List<AbstractFile> ret =
new ArrayList<>();
854 for (UnpackedNode child : rootNode.
children) {
871 void addDerivedFilesToCase() throws TskCoreException {
873 for (UnpackedNode child : rootNode.
children) {
879 final String fileName = node.getFileName();
882 DerivedFile df = fileManager.
addDerivedFile(fileName, node.getLocalRelPath(), node.getSize(),
883 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
887 }
catch (TskCoreException ex) {
888 logger.log(Level.SEVERE,
"Error adding a derived file to db:" + fileName, ex);
889 throw new TskCoreException(
890 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
907 private List<UnpackedNode>
children =
new ArrayList<>();
908 private String localRelPath =
"";
910 private long ctime, crtime, atime, mtime;
952 void addDerivedInfo(
long size,
954 long ctime,
long crtime,
long atime,
long mtime, String relLocalPath) {
958 this.crtime = crtime;
961 this.localRelPath = relLocalPath;
964 void setFile(AbstractFile file) {
975 UnpackedNode getChild(String childFileName) {
976 UnpackedNode ret = null;
977 for (UnpackedNode child : children) {
978 if (child.fileName.equals(childFileName)) {
1014 private final List<Archive>
archives =
new ArrayList<>();
1023 Archive findArchive(
long objectId) {
1025 if (ar.objectId == objectId) {
1041 Archive addArchive(Archive parent,
long objectId) {
1042 Archive child =
new Archive(parent, objectId);
1043 archives.add(child);
1052 List<Archive> children;
1055 this.parent = parent;
1056 this.objectId = objectId;
1057 children =
new ArrayList<>();
1058 if (parent != null) {
1059 parent.children.add(
this);
1060 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 final int DISK_FREE_SPACE_UNKNOWN
static Case getCurrentCase()