19 package org.sleuthkit.autopsy.modules.embeddedfileextractor;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.nio.charset.Charset;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.List;
35 import java.util.concurrent.ConcurrentHashMap;
36 import java.util.logging.Level;
37 import net.sf.sevenzipjbinding.ArchiveFormat;
38 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
39 import net.sf.sevenzipjbinding.ExtractAskMode;
40 import net.sf.sevenzipjbinding.ExtractOperationResult;
41 import net.sf.sevenzipjbinding.IArchiveExtractCallback;
42 import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
43 import net.sf.sevenzipjbinding.IInArchive;
44 import net.sf.sevenzipjbinding.ISequentialOutStream;
45 import net.sf.sevenzipjbinding.PropID;
46 import net.sf.sevenzipjbinding.SevenZip;
47 import net.sf.sevenzipjbinding.SevenZipException;
48 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
49 import org.apache.tika.Tika;
50 import org.apache.tika.parser.txt.CharsetDetector;
51 import org.apache.tika.parser.txt.CharsetMatch;
52 import org.netbeans.api.progress.ProgressHandle;
53 import org.openide.util.NbBundle;
54 import org.openide.util.NbBundle.Messages;
72 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
74 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
75 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
76 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
82 import org.
sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
90 class SevenZipExtractor {
92 private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
94 private static final String MODULE_NAME = EmbeddedFileExtractorModuleFactory.getModuleName();
97 private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
98 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
99 private static final String ENCRYPTION_FULL = EncryptionDetectionModuleFactory.PASSWORD_PROTECT_MESSAGE;
102 private static final int MAX_DEPTH = 4;
103 private static final int MAX_COMPRESSION_RATIO = 600;
104 private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
105 private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L;
107 private IngestServices services = IngestServices.getInstance();
108 private final IngestJobContext context;
109 private final FileTypeDetector fileTypeDetector;
110 private final FileTaskExecutor fileTaskExecutor;
112 private String moduleDirRelative;
113 private String moduleDirAbsolute;
115 private Blackboard blackboard;
117 private ProgressHandle progress;
118 private int numItems;
119 private String currentArchiveName;
133 XRAR(
"application/x-rar-compressed");
138 this.mimeType = mimeType;
143 return this.mimeType;
168 SevenZipExtractor(
IngestJobContext context,
FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute, FileTaskExecutor fileTaskExecutor)
throws SevenZipNativeInitializationException {
169 if (!SevenZip.isInitializedSuccessfully()) {
170 throw new SevenZipNativeInitializationException(
"SevenZip has not been previously initialized.");
172 this.context = context;
173 this.fileTypeDetector = fileTypeDetector;
174 this.moduleDirRelative = moduleDirRelative;
175 this.moduleDirAbsolute = moduleDirAbsolute;
176 this.fileTaskExecutor = fileTaskExecutor;
187 boolean isSevenZipExtractionSupported(AbstractFile file) {
188 String fileMimeType = fileTypeDetector.getMIMEType(file);
189 for (SupportedArchiveExtractionFormats mimeType : SupportedArchiveExtractionFormats.values()) {
190 if (checkForIngestCancellation(file)) {
193 if (mimeType.toString().equals(fileMimeType)) {
200 boolean isSevenZipExtractionSupported(String mimeType) {
201 for (SupportedArchiveExtractionFormats supportedMimeType : SupportedArchiveExtractionFormats.values()) {
202 if (mimeType.contains(supportedMimeType.toString())) {
219 private boolean checkForIngestCancellation(AbstractFile file) {
220 if (fileTaskExecutor != null && context != null && context.fileIngestIsCancelled()) {
221 logger.log(Level.INFO,
"Ingest was cancelled. Results extracted from the following archive file may be incomplete. Name: {0}Object ID: {1}",
new Object[]{file.getName(), file.getId()});
250 private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, IInArchive inArchive,
int inArchiveItemIndex, ConcurrentHashMap<Long, Archive> depthMap, String escapedFilePath) {
260 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) {
265 final Long archiveItemSize = (Long) inArchive.getProperty(
266 inArchiveItemIndex, PropID.SIZE);
269 if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
273 final Long archiveItemPackedSize = (Long) inArchive.getProperty(
274 inArchiveItemIndex, PropID.PACKED_SIZE);
276 if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
277 logger.log(Level.WARNING,
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}",
278 new Object[]{archiveFile.getName(), (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH)});
282 int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
284 if (cRatio >= MAX_COMPRESSION_RATIO) {
285 Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId());
286 String details = NbBundle.getMessage(SevenZipExtractor.class,
287 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails",
288 cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
290 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath);
296 }
catch (SevenZipException ex) {
297 logger.log(Level.WARNING,
"Error getting archive item size and cannot detect if zipbomb. ", ex);
313 private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) {
314 rootArchive.flagAsZipBomb();
315 logger.log(Level.INFO, details);
317 String setName =
"Possible Zip Bomb";
319 Collection<BlackboardAttribute> attributes = Arrays.asList(
320 new BlackboardAttribute(
321 TSK_SET_NAME, MODULE_NAME,
323 new BlackboardAttribute(
324 TSK_DESCRIPTION, MODULE_NAME,
325 Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName())),
326 new BlackboardAttribute(
327 TSK_COMMENT, MODULE_NAME,
330 if (!blackboard.artifactExists(archiveFile, TSK_INTERESTING_FILE_HIT, attributes)) {
332 BlackboardArtifact artifact = rootArchive.getArchiveFile().newAnalysisResult(
333 BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, Score.SCORE_LIKELY_NOTABLE,
336 .getAnalysisResult();
344 blackboard.postArtifact(artifact, MODULE_NAME);
346 String msg = NbBundle.getMessage(SevenZipExtractor.class,
347 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath);
349 services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
351 }
catch (Blackboard.BlackboardException ex) {
352 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
353 MessageNotifyUtil.Notify.error(
354 Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
357 }
catch (TskCoreException ex) {
358 logger.log(Level.SEVERE,
"Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex);
370 private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
372 String detectedFormat;
373 detectedFormat = archiveFile.getMIMEType();
375 if (detectedFormat == null) {
376 logger.log(Level.WARNING,
"Could not detect format for file: {0}", archiveFile);
379 String extension = archiveFile.getNameExtension();
380 if (
"rar".equals(extension))
389 }
else if (detectedFormat.contains(
"application/x-rar-compressed"))
410 private long getRootArchiveId(AbstractFile file)
throws TskCoreException {
411 long id = file.getId();
412 Content parentContent = file.getParent();
413 while (parentContent != null) {
414 id = parentContent.getId();
415 parentContent = parentContent.getParent();
439 private List<AbstractFile> getAlreadyExtractedFiles(AbstractFile archiveFile, String archiveFilePath)
throws TskCoreException, InterruptedException, FileTaskExecutor.FileTaskFailedException {
443 List<AbstractFile> extractedFiles =
new ArrayList<>();
444 File outputDirectory =
new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
445 if (archiveFile.hasChildren() && fileTaskExecutor.exists(outputDirectory)) {
446 Case currentCase = Case.getCurrentCase();
447 FileManager fileManager = currentCase.getServices().getFileManager();
448 extractedFiles.addAll(fileManager.findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath));
450 return extractedFiles;
460 private String getArchiveFilePath(AbstractFile archiveFile) {
461 return archiveFile.getParentPath() + archiveFile.getName();
473 private boolean makeExtractedFilesDirectory(String uniqueArchiveFileName) {
474 boolean success =
true;
475 Path rootDirectoryPath = Paths.get(moduleDirAbsolute, uniqueArchiveFileName);
476 File rootDirectory = rootDirectoryPath.toFile();
478 if (!fileTaskExecutor.exists(rootDirectory)) {
479 success = fileTaskExecutor.mkdirs(rootDirectory);
481 }
catch (SecurityException | FileTaskFailedException | InterruptedException ex) {
482 logger.log(Level.SEVERE, String.format(
"Error creating root extracted files directory %s", rootDirectory), ex);
500 private String getPathInArchive(IInArchive archive,
int inArchiveItemIndex, AbstractFile archiveFile)
throws SevenZipException {
501 String pathInArchive = (String) archive.getProperty(inArchiveItemIndex, PropID.PATH);
503 if (pathInArchive == null || pathInArchive.isEmpty()) {
509 String archName = archiveFile.getName();
510 int dotI = archName.lastIndexOf(
".");
511 String useName = null;
513 String base = archName.substring(0, dotI);
514 String ext = archName.substring(dotI);
515 int colonIndex = ext.lastIndexOf(
":");
516 if (colonIndex != -1) {
519 ext = ext.substring(0, colonIndex);
526 useName = base +
".tar";
533 if (useName == null) {
534 pathInArchive =
"/" + archName +
"/" + Integer.toString(inArchiveItemIndex);
536 pathInArchive =
"/" + useName;
539 return pathInArchive;
542 private byte[] getPathBytesInArchive(IInArchive archive,
int inArchiveItemIndex, AbstractFile archiveFile)
throws SevenZipException {
543 return (byte[]) archive.getProperty(inArchiveItemIndex, PropID.PATH_BYTES);
550 private String getKeyAbstractFile(AbstractFile fileInDatabase) {
551 return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
558 private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
559 return node == null ? null : archiveFilePath +
"/" + node.getFileName();
569 void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
570 unpack(archiveFile, depthMap, null);
584 @Messages({
"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
585 "# {0} - rootArchive",
586 "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
587 boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
588 boolean unpackSuccessful =
true;
589 boolean hasEncrypted =
false;
590 boolean fullEncryption =
true;
591 boolean progressStarted =
false;
592 final String archiveFilePath = getArchiveFilePath(archiveFile);
593 final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
594 HashMap<String, ZipFileStatusWrapper> statusMap =
new HashMap<>();
595 List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
597 currentArchiveName = archiveFile.getName();
599 SevenZipContentReadStream stream = null;
600 progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
604 blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
605 }
catch (NoCurrentCaseException ex) {
606 logger.log(Level.INFO,
"Exception while getting open case.", ex);
607 unpackSuccessful =
false;
608 return unpackSuccessful;
610 if (checkForIngestCancellation(archiveFile)) {
614 List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
615 for (AbstractFile file : existingFiles) {
616 statusMap.put(getKeyAbstractFile(file),
new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
618 }
catch (TskCoreException | FileTaskFailedException | InterruptedException ex) {
619 logger.log(Level.SEVERE, String.format(
"Error checking if %s has already been processed, skipping", escapedArchiveFilePath), ex);
620 unpackSuccessful =
false;
621 return unpackSuccessful;
623 if (checkForIngestCancellation(archiveFile)) {
626 parentAr = depthMap.get(archiveFile.getId());
627 if (parentAr == null) {
628 parentAr =
new Archive(0, archiveFile.getId(), archiveFile);
629 depthMap.put(archiveFile.getId(), parentAr);
631 Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
632 if (rootArchive.isFlaggedAsZipBomb()) {
634 unpackSuccessful =
false;
635 return unpackSuccessful;
636 }
else if (parentAr.getDepth() == MAX_DEPTH) {
637 String details = NbBundle.getMessage(SevenZipExtractor.class,
638 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
639 parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
640 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
641 unpackSuccessful =
false;
642 return unpackSuccessful;
645 if (checkForIngestCancellation(archiveFile)) {
648 IInArchive inArchive = null;
650 stream =
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
654 ArchiveFormat options = get7ZipOptions(archiveFile);
655 if (checkForIngestCancellation(archiveFile)) {
658 if (password == null) {
659 inArchive = SevenZip.openInArchive(options, stream);
661 inArchive = SevenZip.openInArchive(options, stream, password);
663 numItems = inArchive.getNumberOfItems();
664 progress.start(numItems);
665 progressStarted =
true;
666 if (checkForIngestCancellation(archiveFile)) {
670 final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
671 if (!makeExtractedFilesDirectory(uniqueArchiveFileName)) {
676 SevenZipExtractor.UnpackedTree unpackedTree =
new SevenZipExtractor.UnpackedTree(moduleDirRelative +
"/" + uniqueArchiveFileName, archiveFile);
680 freeDiskSpace = services.getFreeDiskSpace();
681 }
catch (NullPointerException ex) {
684 freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
687 Map<Integer, InArchiveItemDetails> archiveDetailsMap =
new HashMap<>();
688 for (
int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
689 if (checkForIngestCancellation(archiveFile)) {
692 progress.progress(String.format(
"%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
693 if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
694 unpackSuccessful =
false;
695 return unpackSuccessful;
698 String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
699 byte[] pathBytesInArchive = getPathBytesInArchive(inArchive, inArchiveItemIndex, archiveFile);
700 UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive, pathBytesInArchive);
701 if (checkForIngestCancellation(archiveFile)) {
704 final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
706 if (isEncrypted && password == null) {
707 logger.log(Level.WARNING,
"Skipping encrypted file in archive: {0}", pathInArchive);
709 unpackSuccessful =
false;
712 fullEncryption =
false;
719 Long archiveItemSize = (Long) inArchive.getProperty(
720 inArchiveItemIndex, PropID.SIZE);
721 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) {
722 String archiveItemPath = (String) inArchive.getProperty(
723 inArchiveItemIndex, PropID.PATH);
724 long newDiskSpace = freeDiskSpace - archiveItemSize;
725 if (newDiskSpace < MIN_FREE_DISK_SPACE) {
726 String msg = NbBundle.getMessage(SevenZipExtractor.class,
727 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
728 escapedArchiveFilePath, archiveItemPath);
729 String details = NbBundle.getMessage(SevenZipExtractor.class,
730 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
731 services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
732 logger.log(Level.INFO,
"Skipping archive item due to insufficient disk space: {0}, {1}",
new String[]{escapedArchiveFilePath, archiveItemPath});
733 logger.log(Level.INFO,
"Available disk space: {0}",
new Object[]{freeDiskSpace});
734 unpackSuccessful =
false;
738 freeDiskSpace = newDiskSpace;
741 if (checkForIngestCancellation(archiveFile)) {
744 final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex);
745 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
746 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
750 File localFile =
new File(localAbsPath);
751 boolean localFileExists;
753 if ((Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER)) {
754 localFileExists = findOrCreateDirectory(localFile);
756 localFileExists = findOrCreateEmptyFile(localFile);
758 }
catch (FileTaskFailedException | InterruptedException ex) {
759 localFileExists =
false;
760 logger.log(Level.SEVERE, String.format(
"Error fiding or creating %s", localFile.getAbsolutePath()), ex);
762 if (checkForIngestCancellation(archiveFile)) {
767 if (!localFileExists) {
768 logger.log(Level.SEVERE, String.format(
"Skipping %s because it could not be created", localFile.getAbsolutePath()));
776 archiveDetailsMap.put(inArchiveItemIndex,
new InArchiveItemDetails(
777 unpackedNode, localAbsPath, localRelPath));
780 int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
781 if (checkForIngestCancellation(archiveFile)) {
784 StandardIArchiveExtractCallback archiveCallBack
785 =
new StandardIArchiveExtractCallback(
786 inArchive, archiveFile, progress,
787 archiveDetailsMap, password, freeDiskSpace);
792 inArchive.extract(extractionIndices,
false, archiveCallBack);
793 if (checkForIngestCancellation(archiveFile)) {
796 unpackSuccessful &= archiveCallBack.wasSuccessful();
798 archiveDetailsMap = null;
803 unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath, parentAr, archiveFile, depthMap);
804 unpackedTree.commitCurrentTransaction();
805 }
catch (TskCoreException | NoCurrentCaseException ex) {
806 logger.log(Level.SEVERE,
"Error populating complete derived file hierarchy from the unpacked dir structure", ex);
808 unpackedTree.rollbackCurrentTransaction();
811 if (checkForIngestCancellation(archiveFile)) {
816 unpackedFiles = unpackedTree.getAllFileObjects();
817 }
catch (SevenZipException | IllegalArgumentException ex) {
818 logger.log(Level.WARNING,
"Error unpacking file: " + archiveFile, ex);
822 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
823 String msg = NbBundle.getMessage(SevenZipExtractor.class,
824 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
826 String details = NbBundle.getMessage(SevenZipExtractor.class,
827 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
828 escapedArchiveFilePath, ex.getMessage());
829 services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
832 if (inArchive != null) {
835 }
catch (SevenZipException e) {
836 logger.log(Level.SEVERE,
"Error closing archive: " + archiveFile, e);
840 if (stream != null) {
843 }
catch (IOException ex) {
844 logger.log(Level.SEVERE,
"Error closing stream after unpacking archive: " + archiveFile, ex);
849 if (progressStarted) {
853 if (checkForIngestCancellation(archiveFile)) {
858 String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
860 BlackboardArtifact artifact = archiveFile.newAnalysisResult(
861 new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED),
863 null, null, encryptionType,
864 Arrays.asList(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType)))
865 .getAnalysisResult();
873 blackboard.postArtifact(artifact, MODULE_NAME);
874 }
catch (Blackboard.BlackboardException ex) {
875 logger.log(Level.SEVERE,
"Unable to post blackboard artifact " + artifact.getArtifactID(), ex);
876 MessageNotifyUtil.Notify.error(
877 Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
880 }
catch (TskCoreException ex) {
881 logger.log(Level.SEVERE,
"Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex);
884 String msg = NbBundle.getMessage(SevenZipExtractor.class,
885 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
886 String details = NbBundle.getMessage(SevenZipExtractor.class,
887 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
888 currentArchiveName, MODULE_NAME);
889 services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
893 if (!unpackedFiles.isEmpty()) {
895 services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
896 if (context != null) {
897 context.addFilesToJob(unpackedFiles);
901 return unpackSuccessful;
911 private boolean findOrCreateDirectory(File directory)
throws FileTaskFailedException, InterruptedException {
912 if (!fileTaskExecutor.exists(directory)) {
913 return fileTaskExecutor.mkdirs(directory);
926 private boolean findOrCreateEmptyFile(File file)
throws FileTaskFailedException, InterruptedException {
927 if (!fileTaskExecutor.exists(file)) {
928 fileTaskExecutor.mkdirs(file.getParentFile());
929 return fileTaskExecutor.createNewFile(file);
935 private Charset detectFilenamesCharset(List<byte[]> byteDatas) {
936 Charset detectedCharset = null;
937 CharsetDetector charsetDetector =
new CharsetDetector();
940 for (byte[] byteData : byteDatas) {
942 byteSum += byteData.length;
944 if (byteSum >= 1000) {
948 byte[] allBytes =
new byte[byteSum];
950 for (
int i = 0; i < fileNum; i++) {
951 byte[] byteData = byteDatas.get(i);
952 System.arraycopy(byteData, 0, allBytes, start, byteData.length);
953 start += byteData.length;
955 charsetDetector.setText(allBytes);
956 CharsetMatch cm = charsetDetector.detect();
957 if (cm.getConfidence() >= 90 && Charset.isSupported(cm.getName())) {
958 detectedCharset = Charset.forName(cm.getName());
960 return detectedCharset;
967 private int[] getExtractableFilesFromDetailsMap(
968 Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
970 Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
971 .toArray(
new Integer[archiveDetailsMap.size()]);
973 return Arrays.stream(wrappedExtractionIndices)
974 .mapToInt(Integer::intValue)
986 private final static class UnpackStream implements ISequentialOutStream {
991 private static final Tika
tika =
new Tika();
992 private String mimeType =
"";
995 this.output =
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
997 this.bytesWritten = 0;
1001 this.output.close();
1002 this.output =
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
1004 this.bytesWritten = 0;
1013 public int write(byte[] bytes)
throws SevenZipException {
1016 if (bytesWritten == 0) {
1017 mimeType = tika.detect(bytes);
1019 output.write(bytes);
1020 this.bytesWritten += bytes.length;
1021 }
catch (IOException ex) {
1022 throw new SevenZipException(
1023 NbBundle.getMessage(SevenZipExtractor.class,
1024 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
1027 return bytes.length;
1034 public void close() throws IOException {
1035 try (EncodedFileOutputStream out = output) {
1052 SevenZipExtractor.UnpackedTree.UnpackedNode
unpackedNode,
1053 String localAbsPath, String localRelPath) {
1077 implements IArchiveExtractCallback, ICryptoGetTextPassword {
1094 private boolean unpackSuccessful =
true;
1097 AbstractFile archiveFile, ProgressHandle progressHandle,
1098 Map<Integer, InArchiveItemDetails> archiveDetailsMap,
1099 String password,
long freeDiskSpace) {
1122 public ISequentialOutStream
getStream(
int inArchiveItemIndex,
1123 ExtractAskMode mode)
throws SevenZipException {
1127 isFolder = (Boolean) inArchive
1128 .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
1129 if (isFolder || mode != ExtractAskMode.EXTRACT) {
1133 final String localAbsPath = archiveDetailsMap.get(
1134 inArchiveItemIndex).getLocalAbsPath();
1142 if (unpackStream != null) {
1147 }
catch (IOException ex) {
1148 logger.log(Level.WARNING, String.format(
"Error opening or setting new stream "
1149 +
"for archive file at %s", localAbsPath), ex.getMessage());
1166 final Date createTime = (Date) inArchive.getProperty(
1167 inArchiveItemIndex, PropID.CREATION_TIME);
1168 final Date accessTime = (Date) inArchive.getProperty(
1169 inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
1170 final Date writeTime = (Date) inArchive.getProperty(
1171 inArchiveItemIndex, PropID.LAST_MODIFICATION_TIME);
1173 createTimeInSeconds = createTime == null ? 0L
1174 : createTime.getTime() / 1000;
1175 modTimeInSeconds = writeTime == null ? 0L
1176 : writeTime.getTime() / 1000;
1177 accessTimeInSeconds = accessTime == null ? 0L
1178 : accessTime.getTime() / 1000;
1180 progressHandle.progress(archiveFile.getName() +
": "
1181 + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
1197 final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
1198 = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
1199 final String localRelPath = archiveDetailsMap.get(
1200 inArchiveItemIndex).getLocalRelPath();
1202 unpackedNode.addDerivedInfo(0,
1203 !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1208 unpackedNode.setMimeType(unpackStream.
getMIMEType());
1211 final String localAbsPath = archiveDetailsMap.get(
1212 inArchiveItemIndex).getLocalAbsPath();
1213 if (result != ExtractOperationResult.OK) {
1214 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
1215 logger.log(Level.WARNING,
"Extraction of : {0} encountered error {1} (file is unallocated and may be corrupt)",
1216 new Object[]{localAbsPath, result});
1218 logger.log(Level.WARNING,
"Extraction of : {0} encountered error {1}",
1219 new Object[]{localAbsPath, result});
1221 unpackSuccessful =
false;
1225 unpackedNode.addDerivedInfo(unpackStream.
getSize(),
1226 !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1230 unpackStream.
close();
1231 }
catch (IOException e) {
1232 logger.log(Level.WARNING,
"Error closing unpack stream for file: {0}", localAbsPath);
1237 public void setTotal(
long value)
throws SevenZipException {
1290 UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
1292 this.rootNode.setFile(archiveFile);
1293 this.rootNode.setFileName(archiveFile.getName());
1294 this.rootNode.setLocalRelPath(localPathRoot);
1306 UnpackedNode addNode(String filePath, byte[] filePathBytes) {
1307 String[] toks = filePath.split(
"[\\/\\\\]");
1308 List<String> tokens =
new ArrayList<>();
1309 for (
int i = 0; i < toks.length; ++i) {
1310 if (!toks[i].isEmpty()) {
1311 tokens.add(toks[i]);
1315 List<byte[]> byteTokens;
1316 if (filePathBytes == null) {
1317 return addNode(rootNode, tokens, null);
1319 byteTokens =
new ArrayList<>(tokens.size());
1321 for (
int i = 0; i < filePathBytes.length; i++) {
1322 if (filePathBytes[i] ==
'/') {
1325 byte[] arr =
new byte[len];
1326 System.arraycopy(filePathBytes, last, arr, 0, len);
1327 byteTokens.add(arr);
1332 int len = filePathBytes.length - last;
1334 byte[] arr =
new byte[len];
1335 System.arraycopy(filePathBytes, last, arr, 0, len);
1336 byteTokens.add(arr);
1339 if (tokens.size() != byteTokens.size()) {
1340 String rootFileInfo =
"(unknown)";
1341 if (rootNode.getFile() != null) {
1342 rootFileInfo = rootNode.getFile().getParentPath() + rootNode.getFile().getName()
1343 +
"(ID: " + rootNode.getFile().getId() +
")";
1345 logger.log(Level.WARNING,
"Could not map path bytes to path string while extracting archive {0} (path string: \"{1}\", bytes: {2})",
1346 new Object[]{rootFileInfo, this.rootNode.getFile().getId(), filePath, bytesToString(filePathBytes)});
1347 return addNode(rootNode, tokens, null);
1351 return addNode(rootNode, tokens, byteTokens);
1362 StringBuilder result =
new StringBuilder();
1363 for (byte b : bytes) {
1364 result.append(String.format(
"%02x", b));
1366 return result.toString();
1379 List<String> tokenPath, List<byte[]> tokenPathBytes) {
1381 if (tokenPath.isEmpty()) {
1386 String childName = tokenPath.remove(0);
1387 byte[] childNameBytes = null;
1388 if (tokenPathBytes != null) {
1389 childNameBytes = tokenPathBytes.remove(0);
1393 if (child == null) {
1395 child.setFileNameBytes(childNameBytes);
1396 parent.addChild(child);
1400 return addNode(child, tokenPath, tokenPathBytes);
1409 List<AbstractFile> getRootFileObjects() {
1410 List<AbstractFile> ret =
new ArrayList<>();
1411 rootNode.getChildren().forEach((child) -> {
1412 ret.add(child.getFile());
1423 List<AbstractFile> getAllFileObjects() {
1424 List<AbstractFile> ret =
new ArrayList<>();
1425 rootNode.getChildren().forEach((child) -> {
1432 list.add(parent.getFile());
1433 parent.getChildren().forEach((child) -> {
1442 void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath, Archive parentAr, AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap)
throws TskCoreException,
NoCurrentCaseException {
1444 for (UnpackedNode child : rootNode.getChildren()) {
1445 updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath, parentAr, archiveFile, depthMap);
1466 private void updateOrAddFileToCaseRec(
UnpackedNode node,
FileManager fileManager, HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath, Archive parentAr, AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap)
throws TskCoreException {
1468 progress.progress(String.format(
"%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
1470 String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
1471 ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
1472 if (existingFile == null) {
1474 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1475 node.isIsFile(), node.getParent().getFile(),
"", MODULE_NAME,
1479 String key = getKeyAbstractFile(existingFile.
getFile());
1482 statusMap.put(key, existingFile);
1486 String mimeType = existingFile.
getFile().getMIMEType().equalsIgnoreCase(
"application/octet-stream") ? null : existingFile.
getFile().getMIMEType();
1488 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1489 node.isIsFile(), mimeType,
"", MODULE_NAME,
1494 df = (DerivedFile) existingFile.
getFile();
1499 logger.log(Level.SEVERE,
"Error adding a derived file to db:" + node.getFileName(), ex);
1500 throw new TskCoreException(
1501 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
1502 node.getFileName()), ex);
1506 if (node.getChildren().size() > 0) {
1508 ArrayList<byte[]> byteDatas =
new ArrayList<>();
1510 byte[] childBytes = child.getFileNameBytes();
1511 if (childBytes != null) {
1512 byteDatas.add(childBytes);
1514 names += child.getFileName();
1516 Charset detectedCharset = detectFilenamesCharset(byteDatas);
1519 if (detectedCharset != null && detectedCharset.canEncode()) {
1521 byte[] childBytes = child.getFileNameBytes();
1522 if (childBytes != null) {
1523 String decodedName =
new String(childBytes, detectedCharset);
1524 child.setFileName(decodedName);
1531 if (isSevenZipExtractionSupported(node.getMimeType())) {
1532 Archive child =
new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
1533 parentAr.addChild(child);
1534 depthMap.put(node.getFile().getId(), child);
1539 updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath), parentAr, archiveFile, depthMap);
1555 if (currentTransaction == null) {
1559 if (transactionCounter > MAX_TRANSACTION_SIZE) {
1564 transactionCounter++;
1576 transactionCounter = 0;
1578 throw new TskCoreException(
"Case is closed");
1588 if (currentTransaction != null) {
1589 currentTransaction.commit();
1590 currentTransaction = null;
1598 if (currentTransaction != null) {
1600 currentTransaction.rollback();
1601 currentTransaction = null;
1602 }
catch (TskCoreException ex) {
1616 private final List<UnpackedNode>
children =
new ArrayList<>();
1617 private String localRelPath =
"";
1619 private long ctime, crtime, atime, mtime;
1621 private String mimeType =
"";
1632 this.localRelPath = parent.getLocalRelPath() + File.separator +
fileName;
1651 void setFileName(String fileName) {
1660 void addChild(UnpackedNode child) {
1661 children.add(child);
1670 List<UnpackedNode> getChildren() {
1679 UnpackedNode getParent() {
1683 void addDerivedInfo(
long size,
1685 long ctime,
long crtime,
long atime,
long mtime, String relLocalPath) {
1689 this.crtime = crtime;
1692 this.localRelPath = relLocalPath;
1695 void setFile(AbstractFile file) {
1699 void setMimeType(String mimeType) {
1703 String getMimeType() {
1714 UnpackedNode getChild(String childFileName) {
1715 UnpackedNode ret = null;
1716 for (UnpackedNode child : children) {
1717 if (child.getFileName().equals(childFileName)) {
1725 String getFileName() {
1729 AbstractFile getFile() {
1733 String getLocalRelPath() {
1743 void setLocalRelPath(String localRelativePath) {
1744 localRelPath = localRelativePath;
1751 boolean isIsFile() {
1755 void setFileNameBytes(byte[] fileNameBytes) {
1756 if (fileNameBytes != null) {
1757 this.fileNameBytes = Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1761 byte[] getFileNameBytes() {
1762 if (fileNameBytes == null) {
1765 return Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1774 static class Archive {
1777 private final int depth;
1778 private final List<Archive> children;
1779 private final long rootArchiveId;
1780 private boolean flaggedAsZipBomb =
false;
1781 private final AbstractFile archiveFile;
1795 Archive(
int depth,
long rootArchiveId, AbstractFile archiveFile) {
1796 this.children =
new ArrayList<>();
1798 this.rootArchiveId = rootArchiveId;
1799 this.archiveFile = archiveFile;
1808 void addChild(Archive child) {
1809 children.add(child);
1816 synchronized void flagAsZipBomb() {
1817 flaggedAsZipBomb =
true;
1825 synchronized boolean isFlaggedAsZipBomb() {
1826 return flaggedAsZipBomb;
1834 AbstractFile getArchiveFile() {
1843 long getRootArchiveId() {
1844 return rootArchiveId;
1852 long getObjectId() {
1853 return archiveFile.getId();
1885 abstractFile = file;
FileManager getFileManager()
SleuthkitCase getSleuthkitCase()
static Case getCurrentCaseThrows()