19 package org.sleuthkit.autopsy.modules.embeddedfileextractor;
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;
66 class SevenZipExtractor {
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",};
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");
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;
84 private ArchiveDepthCountTree archiveDepthCountTree;
86 private String moduleDirRelative;
87 private String moduleDirAbsolute;
89 private Blackboard blackboard;
91 private String getLocalRootAbsPath(String uniqueArchiveFileName) {
92 return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
107 XRAR(
"application/x-rar-compressed");
112 this.mimeType = mimeType;
117 return this.mimeType;
122 SevenZipExtractor(
IngestJobContext context,
FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute)
throws SevenZipNativeInitializationException {
123 if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
124 SevenZip.initSevenZipFromPlatformJAR();
126 this.context = context;
127 this.fileTypeDetector = fileTypeDetector;
128 this.moduleDirRelative = moduleDirRelative;
129 this.moduleDirAbsolute = moduleDirAbsolute;
130 this.archiveDepthCountTree =
new ArchiveDepthCountTree();
143 boolean isSevenZipExtractionSupported(AbstractFile abstractFile) {
145 String abstractFileMimeType = fileTypeDetector.getFileType(abstractFile);
146 for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) {
147 if (s.toString().equals(abstractFileMimeType)) {
152 }
catch (TskCoreException ex) {
153 logger.log(Level.WARNING,
"Error executing FileTypeDetector.getFileType()", ex);
157 final String extension = abstractFile.getNameExtension();
158 for (String supportedExtension : SUPPORTED_EXTENSIONS) {
159 if (extension.equals(supportedExtension)) {
179 private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) {
181 final Long archiveItemSize = archiveFileItem.getSize();
184 if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
188 final Long archiveItemPackedSize = archiveFileItem.getPackedSize();
190 if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
191 logger.log(Level.WARNING,
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}",
new Object[]{archiveFile.getName(), archiveFileItem.getPath()});
195 int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
197 if (cRatio >= MAX_COMPRESSION_RATIO) {
198 String itemName = archiveFileItem.getPath();
199 logger.log(Level.INFO,
"Possible zip bomb detected, compression ration: {0} for in archive item: {1}",
new Object[]{cRatio, itemName});
200 String msg = NbBundle.getMessage(SevenZipExtractor.class,
201 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
204 path = archiveFile.getUniquePath();
205 }
catch (TskCoreException ex) {
206 path = archiveFile.getParentPath() + archiveFile.getName();
208 String details = NbBundle.getMessage(SevenZipExtractor.class,
209 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
211 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
217 }
catch (SevenZipException ex) {
218 logger.log(Level.WARNING,
"Error getting archive item size and cannot detect if zipbomb. ", ex);
231 private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
233 String detectedFormat = null;
234 detectedFormat = archiveFile.getMIMEType();
236 if (detectedFormat == null) {
237 logger.log(Level.WARNING,
"Could not detect format for file: {0}", archiveFile);
240 String extension = archiveFile.getNameExtension();
241 if (
"rar".equals(extension))
250 }
else if (detectedFormat.contains(
"application/x-rar-compressed"))
269 @Messages({
"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."})
270 void unpack(AbstractFile archiveFile) {
271 blackboard = Case.getCurrentCase().getServices().getBlackboard();
272 String archiveFilePath;
274 archiveFilePath = archiveFile.getUniquePath();
275 }
catch (TskCoreException ex) {
276 archiveFilePath = archiveFile.getParentPath() + archiveFile.getName();
281 if (archiveFile.hasChildren()) {
283 if (
new File(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
284 logger.log(Level.INFO,
"File already has been processed as it has children and local unpacked file, skipping: {0}", archiveFilePath);
288 }
catch (TskCoreException e) {
289 logger.log(Level.INFO,
"Error checking if file already has been processed, skipping: {0}", archiveFilePath);
293 List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
296 final long archiveId = archiveFile.getId();
297 SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = archiveDepthCountTree.findArchive(archiveId);
298 if (parentAr == null) {
299 parentAr = archiveDepthCountTree.addArchive(null, archiveId);
300 }
else if (parentAr.getDepth() == MAX_DEPTH) {
301 String msg = NbBundle.getMessage(SevenZipExtractor.class,
302 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
303 String details = NbBundle.getMessage(SevenZipExtractor.class,
304 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
305 parentAr.getDepth(), archiveFilePath);
307 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
311 boolean hasEncrypted =
false;
312 boolean fullEncryption =
true;
314 ISevenZipInArchive inArchive = null;
315 SevenZipContentReadStream stream = null;
317 final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
318 int processedItems = 0;
320 boolean progressStarted =
false;
322 stream =
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
327 ArchiveFormat options = get7ZipOptions(archiveFile);
328 inArchive = SevenZip.openInArchive(options, stream);
330 int numItems = inArchive.getNumberOfItems();
331 logger.log(Level.INFO,
"Count of items in archive: {0}: {1}",
new Object[]{archiveFilePath, numItems});
332 progress.start(numItems);
333 progressStarted =
true;
335 final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
338 final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
339 final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
340 final File localRoot =
new File(localRootAbsPath);
341 if (!localRoot.exists()) {
344 }
catch (SecurityException e) {
345 logger.log(Level.SEVERE,
"Error setting up output path for archive root: {0}", localRootAbsPath);
352 SevenZipExtractor.UnpackedTree unpackedTree =
new SevenZipExtractor.UnpackedTree(moduleDirRelative +
"/" + uniqueArchiveFileName, archiveFile);
354 long freeDiskSpace = services.getFreeDiskSpace();
358 for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
359 String pathInArchive = item.getPath();
361 if (pathInArchive == null || pathInArchive.isEmpty()) {
367 String archName = archiveFile.getName();
368 int dotI = archName.lastIndexOf(
".");
369 String useName = null;
371 String base = archName.substring(0, dotI);
372 String ext = archName.substring(dotI);
373 int colonIndex = ext.lastIndexOf(
":");
374 if (colonIndex != -1) {
377 ext = ext.substring(0, colonIndex);
384 useName = base +
".tar";
392 if (useName == null) {
393 pathInArchive =
"/" + archName +
"/" + Integer.toString(itemNumber);
395 pathInArchive =
"/" + useName;
398 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
399 archiveFilePath, pathInArchive);
400 logger.log(Level.WARNING, msg);
403 archiveFilePath = FileUtil.escapeFileName(archiveFilePath);
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;
432 Long size = item.getSize();
436 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size != null && size > 0) {
437 long newDiskSpace = freeDiskSpace - size;
438 if (newDiskSpace < MIN_FREE_DISK_SPACE) {
439 String msg = NbBundle.getMessage(SevenZipExtractor.class,
440 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
441 archiveFilePath, fileName);
442 String details = NbBundle.getMessage(SevenZipExtractor.class,
443 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
445 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
446 logger.log(Level.INFO,
"Skipping archive item due to insufficient disk space: {0}, {1}",
new Object[]{archiveFilePath, fileName});
447 logger.log(Level.INFO,
"Available disk space: {0}",
new Object[]{freeDiskSpace});
451 freeDiskSpace = newDiskSpace;
455 final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (item.getItemIndex() / 1000) + File.separator + item.getItemIndex() +
"_" +
new File(pathInArchive).getName());
458 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
459 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
462 File localFile =
new java.io.File(localAbsPath);
464 if (!localFile.exists()) {
469 localFile.getParentFile().mkdirs();
471 localFile.createNewFile();
472 }
catch (IOException e) {
473 logger.log(Level.SEVERE,
"Error creating extracted file: " + localFile.getAbsolutePath(), e);
476 }
catch (SecurityException e) {
477 logger.log(Level.SEVERE,
"Error setting up output path for unpacked file: {0}", pathInArchive);
483 if (localFile.exists() ==
false) {
487 final Date createTime = item.getCreationTime();
488 final Date accessTime = item.getLastAccessTime();
489 final Date writeTime = item.getLastWriteTime();
490 final long createtime = createTime == null ? 0L : createTime.getTime() / 1000;
491 final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
492 final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
495 SevenZipExtractor.UnpackStream unpackStream = null;
499 unpackStream =
new SevenZipExtractor.KnownSizeUnpackStream(localAbsPath, size);
501 unpackStream =
new SevenZipExtractor.UnknownSizeUnpackStream(localAbsPath, freeDiskSpace);
503 item.extractSlow(unpackStream);
504 }
catch (Exception e) {
506 logger.log(Level.WARNING,
"Could not extract file from archive: " + localAbsPath, e);
508 if (unpackStream != null) {
510 unpackedNode.addDerivedInfo(unpackStream.getSize(), !isDir,
511 0L, createtime, accesstime, modtime, localRelPath);
512 unpackStream.close();
516 unpackedNode.addDerivedInfo(0, !isDir,
517 0L, createtime, accesstime, modtime, localRelPath);
527 unpackedTree.addDerivedFilesToCase();
528 unpackedFiles = unpackedTree.getAllFileObjects();
531 for (AbstractFile unpackedFile : unpackedFiles) {
532 if (isSevenZipExtractionSupported(unpackedFile)) {
533 archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId());
537 }
catch (TskCoreException e) {
538 logger.log(Level.SEVERE,
"Error populating complete derived file hierarchy from the unpacked dir structure");
542 }
catch (SevenZipException ex) {
543 logger.log(Level.WARNING,
"Error unpacking file: {0}", archiveFile);
547 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
548 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
549 archiveFile.getName());
550 String details = NbBundle.getMessage(SevenZipExtractor.class,
551 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
552 archiveFilePath, ex.getMessage());
553 services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
556 if (inArchive != null) {
559 }
catch (SevenZipException e) {
560 logger.log(Level.SEVERE,
"Error closing archive: " + archiveFile, e);
564 if (stream != null) {
567 }
catch (IOException ex) {
568 logger.log(Level.SEVERE,
"Error closing stream after unpacking archive: " + archiveFile, ex);
573 if (progressStarted) {
580 String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
582 BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
583 artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType));
587 blackboard.indexArtifact(artifact);
588 }
catch (Blackboard.BlackboardException ex) {
589 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
590 MessageNotifyUtil.Notify.error(
591 Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
594 services.fireModuleDataEvent(
new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
595 }
catch (TskCoreException ex) {
596 logger.log(Level.SEVERE,
"Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex);
599 String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
600 String details = NbBundle.getMessage(SevenZipExtractor.class,
601 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
602 archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
603 services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
607 if (!unpackedFiles.isEmpty()) {
609 services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
610 context.addFilesToJob(unpackedFiles);
617 private abstract static class UnpackStream implements ISequentialOutStream {
626 }
catch (IOException ex) {
627 logger.log(Level.SEVERE,
"Error writing extracted file: " + localAbsPath, ex);
632 public abstract long getSize();
634 OutputStream getOutput() {
638 String getLocalAbsPath() {
643 if (output != null) {
647 }
catch (IOException e) {
648 logger.log(Level.SEVERE,
"Error closing unpack stream for file: {0}", localAbsPath);
674 public int write(byte[] bytes)
throws SevenZipException {
680 getOutput().write(bytes);
684 this.bytesWritten += bytes.length;
685 this.freeDiskSpace -= bytes.length;
687 this.outOfSpace =
true;
688 logger.log(Level.INFO, NbBundle.getMessage(
689 SevenZipExtractor.class,
690 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
691 throw new SevenZipException(
692 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
694 }
catch (IOException ex) {
695 throw new SevenZipException(
696 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
697 getLocalAbsPath()), ex);
704 if (getOutput() != null) {
708 if (this.outOfSpace) {
709 Files.delete(Paths.get(getLocalAbsPath()));
711 }
catch (IOException e) {
712 logger.log(Level.SEVERE,
"Error closing unpack stream for file: {0}", getLocalAbsPath());
736 public int write(byte[] bytes)
throws SevenZipException {
738 getOutput().write(bytes);
739 }
catch (IOException ex) {
740 throw new SevenZipException(
741 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
742 getLocalAbsPath()), ex);
768 this.rootNode.setFile(archiveFile);
783 String[] toks = filePath.split(
"[\\/\\\\]");
784 List<String> tokens =
new ArrayList<>();
785 for (
int i = 0; i < toks.length; ++i) {
786 if (!toks[i].isEmpty()) {
790 return addNode(rootNode, tokens);
803 if (tokenPath.isEmpty()) {
808 String childName = tokenPath.remove(0);
816 return addNode(child, tokenPath);
825 List<AbstractFile> getRootFileObjects() {
826 List<AbstractFile> ret =
new ArrayList<>();
827 for (UnpackedNode child : rootNode.
children) {
828 ret.add(child.getFile());
839 List<AbstractFile> getAllFileObjects() {
840 List<AbstractFile> ret =
new ArrayList<>();
841 for (UnpackedNode child : rootNode.
children) {
860 for (UnpackedNode child : rootNode.
children) {
866 final String fileName = node.getFileName();
870 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
875 }
catch (TskCoreException ex) {
876 logger.log(Level.SEVERE,
"Error adding a derived file to db:" + fileName, ex);
877 throw new TskCoreException(
878 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
895 private List<UnpackedNode>
children =
new ArrayList<>();
896 private String localRelPath =
"";
898 private long ctime, crtime, atime, mtime;
940 void addDerivedInfo(
long size,
942 long ctime,
long crtime,
long atime,
long mtime, String relLocalPath) {
946 this.crtime = crtime;
949 this.localRelPath = relLocalPath;
963 UnpackedNode getChild(String childFileName) {
964 UnpackedNode ret = null;
965 for (UnpackedNode child : children) {
966 if (child.fileName.equals(childFileName)) {
1002 private final List<Archive>
archives =
new ArrayList<>();
1011 Archive findArchive(
long objectId) {
1013 if (ar.objectId == objectId) {
1029 Archive addArchive(Archive parent,
long objectId) {
1030 Archive child =
new Archive(parent, objectId);
1031 archives.add(child);
1040 List<Archive> children;
1043 this.parent = parent;
1044 this.objectId = objectId;
1045 children =
new ArrayList<>();
1046 if (parent != null) {
1047 parent.children.add(
this);
1048 this.depth = parent.depth + 1;
FileManager getFileManager()
static final int DISK_FREE_SPACE_UNKNOWN
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, TskData.EncodingType encodingType)
static Case getCurrentCase()