diff --git a/Experimental/release/bulk_extractor_1_5_3/32-bit/bulk_extractor.exe b/Experimental/release/bulk_extractor_1_5_3/32-bit/bulk_extractor.exe new file mode 100644 index 0000000000..36d81205b0 Binary files /dev/null and b/Experimental/release/bulk_extractor_1_5_3/32-bit/bulk_extractor.exe differ diff --git a/Experimental/release/bulk_extractor_1_5_3/64-bit/bulk_extractor.exe b/Experimental/release/bulk_extractor_1_5_3/64-bit/bulk_extractor.exe new file mode 100644 index 0000000000..81824ee863 Binary files /dev/null and b/Experimental/release/bulk_extractor_1_5_3/64-bit/bulk_extractor.exe differ diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettings.java b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettings.java new file mode 100755 index 0000000000..cae6b6f9a2 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettings.java @@ -0,0 +1,62 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.ingestmodules.bulkextractor; + +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * Ingest job settings for the Bulk Extractor ingest module. + */ +final class BulkExtractorIngestJobSettings implements IngestModuleIngestJobSettings { + + private static final long serialVersionUID = 1L; + private final boolean onlyProcessUnallocatedSpace; + + /** + * Constructs serializable ingest job settings for the Bulk Extractor ingest + * module. + * + * @param onlyProcessUnallocatedSpace Whether or not only unallocated space + * should be processed; if not, the + * entire disk image is scanned. + */ + BulkExtractorIngestJobSettings(boolean onlyProcessUnallocatedSpace) { + this.onlyProcessUnallocatedSpace = onlyProcessUnallocatedSpace; + } + + /** + * @inheritDoc + */ + @Override + public long getVersionNumber() { + return BulkExtractorIngestJobSettings.serialVersionUID; + } + + /** + * Queries whether or not to process only unallocated space; if not, the + * entire disk image is scanned. + * + * @return True if only unallocated space is to be processed, false + * otherwise. + */ + boolean shouldOnlyProcessUnallocatedSpace() { + return this.onlyProcessUnallocatedSpace; + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettingsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettingsPanel.form new file mode 100755 index 0000000000..dd7822a9d0 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettingsPanel.form @@ -0,0 +1,45 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettingsPanel.java new file mode 100755 index 0000000000..38c742490d --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestJobSettingsPanel.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.ingestmodules.bulkextractor; + +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * Ingest job settings panel for the Bulk Extractor ingest module. + */ +final class BulkExtractorIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { + + /** + * Constructs an ingest job settings panel for the Bulk Extractor ingest + * module. + * + * @param settings The settings to use to initialize the panel. + */ + BulkExtractorIngestJobSettingsPanel(BulkExtractorIngestJobSettings settings) { + initComponents(); + this.processUnallocatedSpaceCheckBox.setSelected(settings.shouldOnlyProcessUnallocatedSpace()); + } + + /** + * @inheritDoc + */ + @Override + public IngestModuleIngestJobSettings getSettings() { + return new BulkExtractorIngestJobSettings(this.processUnallocatedSpaceCheckBox.isSelected()); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + processUnallocatedSpaceCheckBox = new javax.swing.JCheckBox(); + + org.openide.awt.Mnemonics.setLocalizedText(processUnallocatedSpaceCheckBox, org.openide.util.NbBundle.getMessage(BulkExtractorIngestJobSettingsPanel.class, "BulkExtractorIngestJobSettingsPanel.processUnallocatedSpaceCheckBox.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(processUnallocatedSpaceCheckBox) + .addContainerGap(83, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(processUnallocatedSpaceCheckBox) + .addContainerGap(151, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox processUnallocatedSpaceCheckBox; + // End of variables declaration//GEN-END:variables +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestModuleFactory.java b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestModuleFactory.java new file mode 100755 index 0000000000..947d375c2f --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/BulkExtractorIngestModuleFactory.java @@ -0,0 +1,129 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.ingestmodules.bulkextractor; + +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * A factory for creating instances of data source and file ingest modules that + * use Bulk Extractor to scan either an entire disk image or unallocated space + * files, respectively. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public class BulkExtractorIngestModuleFactory extends IngestModuleFactoryAdapter { + + /** + * @inheritDoc + */ + @Override + public String getModuleDisplayName() { + return Utilities.getModuleName(); + } + + /** + * @inheritDoc + */ + @Override + public String getModuleDescription() { + return NbBundle.getMessage(BulkExtractorIngestModuleFactory.class, "BulkExtractorIngestModuleFactory.moduleDescription"); + } + + /** + * @inheritDoc + */ + @Override + public String getModuleVersionNumber() { + return Utilities.getVersion(); + } + + /** + * @inheritDoc + */ + @Override + public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { + // Process the entire image by default. + return new BulkExtractorIngestJobSettings(false); + } + + /** + * @inheritDoc + */ + @Override + public boolean hasIngestJobSettingsPanel() { + return true; + } + + /** + * @inheritDoc + */ + @Override + public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { + if (!(settings instanceof BulkExtractorIngestJobSettings)) { + throw new IllegalArgumentException("Settings not instanceof src.org.sleuthkit.autopsy.bulkextractor.BulkExtractorIngestJobSettings"); // NON_NLS + } + return new BulkExtractorIngestJobSettingsPanel((BulkExtractorIngestJobSettings) settings); + } + + /** + * @inheritDoc + */ + @Override + public boolean isDataSourceIngestModuleFactory() { + return true; + } + + /** + * @inheritDoc + */ + @Override + public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { + if (!(settings instanceof BulkExtractorIngestJobSettings)) { + throw new IllegalArgumentException("Settings not instanceof src.org.sleuthkit.autopsy.bulkextractor.BulkExtractorIngestJobSettings"); // NON_NLS + } + return new DiskImageIngestModule((BulkExtractorIngestJobSettings) settings); + } + + /** + * @inheritDoc + */ + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + /** + * @inheritDoc + */ + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + if (!(settings instanceof BulkExtractorIngestJobSettings)) { + throw new IllegalArgumentException("Settings not instanceof src.org.sleuthkit.autopsy.bulkextractor.BulkExtractorIngestJobSettings"); // NON_NLS + } + return new UnallocatedSpaceIngestModule((BulkExtractorIngestJobSettings) settings); + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/Bundle.properties new file mode 100755 index 0000000000..e4bea1932a --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/Bundle.properties @@ -0,0 +1,9 @@ +OpenIDE-Module-Name=Bulk Extractor Ingest Module +OpenIDE-Module-Display-Category=Ingest Module +OpenIDE-Module-Long-Description=Bulk Extractor ingest module. \n\n Runs the Bulk Extractor executable against an image data source. +OpenIDE-Module-Short-Description=Bulk Extractor ingest module. +Utilities.moduleName=Bulk Extractor +BulkExtractorIngestModuleFactory.moduleDescription=Runs the Bulk Extractor executable against an image data source. +BulkExtractorIngestJobSettingsPanel.processUnallocatedSpaceCheckBox.text=Only process unallocated space +BulkExtractorIngestModule.processTerminated=Bulk Extractor process was terminated due to exceeding max allowable run time when scanning +BulkExtractorIngestModule.moduleError=Bulk Extractor Module Error diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/DiskImageIngestModule.java b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/DiskImageIngestModule.java new file mode 100755 index 0000000000..5761048646 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/DiskImageIngestModule.java @@ -0,0 +1,147 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.ingestmodules.bulkextractor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.ingest.ProcTerminationCode; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A data source ingest module that runs the Bulk Extractor executable with an + * entire disk image as input. + */ +public class DiskImageIngestModule implements DataSourceIngestModule { + + private static final Logger logger = Logger.getLogger(DiskImageIngestModule.class.getName()); + private final BulkExtractorIngestJobSettings settings; + private IngestJobContext context; + private Path bulkExtractorPath; + private Path rootOutputDirPath; + + /** + * Constructs a Bulk Extractor disk image ingest module. + * + * @param settings The settings for the ingest module. + */ + DiskImageIngestModule(BulkExtractorIngestJobSettings settings) { + this.settings = settings; + } + + /** + * @inheritDoc + */ + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + + // Only process data sources that are disk images the user wants scanned + // in their entirety. Scans of unallocated space only are performed by + // a file ingest module. + if (!(context.getDataSource() instanceof Image) || this.settings.shouldOnlyProcessUnallocatedSpace()) { + return; + } + + this.bulkExtractorPath = Utilities.locateBulkExtractorExecutable(); + this.rootOutputDirPath = Utilities.createOutputDirectoryForDataSource(this.context.getDataSource().getName() + "_" + + this.context.getDataSource().getId()); + } + + /** + * @inheritDoc + */ + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { + // Only process data sources that are disk images the user wants scanned + // in their entirety. Scans of unallocated space only are performed by + // a file ingest module. + if (!(dataSource instanceof Image) || this.settings.shouldOnlyProcessUnallocatedSpace()) { + return ProcessResult.OK; + } + + /** + * Not sure how long it will take Bulk Extractor to complete. + */ + progressBar.switchToIndeterminate(); + + try { + // Verify initialization succeeded. + if ((null == this.bulkExtractorPath) || (null == this.rootOutputDirPath)) { + DiskImageIngestModule.logger.log(Level.SEVERE, "Bulk Extractor disk image ingest module called after failed start up"); // NON-NLS + return ProcessResult.ERROR; + } + + // Make an output directory for Bulk Extractor. + Path outputDirPath = Utilities.getOutputSubdirectoryPath(this.rootOutputDirPath, dataSource.getId()); + Files.createDirectories(outputDirPath); + + // Scan the disk image file with Bulk Extractor. + Image image = (Image) dataSource; + String imageFilePath = image.getPaths()[0]; + DataSourceIngestModuleProcessTerminator terminator = new DataSourceIngestModuleProcessTerminator(this.context, true); + int exitValue = Utilities.runBulkExtractor(this.bulkExtractorPath, outputDirPath, Paths.get(imageFilePath), terminator); + + if (terminator.getTerminationCode() == ProcTerminationCode.TIME_OUT) { + String msg = NbBundle.getMessage(this.getClass(), "BulkExtractorIngestModule.processTerminated") + image.getName(); // NON-NLS + MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "BulkExtractorIngestModule.moduleError"), msg); // NON-NLS + DiskImageIngestModule.logger.log(Level.SEVERE, msg); + FileUtil.deleteDir(new File(outputDirPath.toAbsolutePath().toString())); + return IngestModule.ProcessResult.ERROR; + } + + if (0 != exitValue) { + FileUtil.deleteDir(new File(outputDirPath.toAbsolutePath().toString())); + if (this.context.dataSourceIngestIsCancelled()) { + return ProcessResult.OK; + } else { + DiskImageIngestModule.logger.log(Level.SEVERE, "Bulk Extractor returned error exit value = {0} when scanning {1}", new Object[]{exitValue, image.getName()}); // NON-NLS + return ProcessResult.ERROR; + } + } + + if (!Utilities.isDirectoryEmpty(outputDirPath)) { + // Add the output directory to the case as an Autopsy report. + Case.getCurrentCase().addReport(outputDirPath.toAbsolutePath().toString(), Utilities.getModuleName(), Utilities.getReportName(image.getName())); + } + + return ProcessResult.OK; + + } catch (InterruptedException | IOException | SecurityException | UnsupportedOperationException | TskCoreException ex) { + DiskImageIngestModule.logger.log(Level.SEVERE, "Error processing " + dataSource.getName() + " with Bulk Extractor", ex); // NON-NLS + return ProcessResult.ERROR; + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/UnallocatedSpaceIngestModule.java b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/UnallocatedSpaceIngestModule.java new file mode 100755 index 0000000000..a8a7334f3e --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/UnallocatedSpaceIngestModule.java @@ -0,0 +1,206 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.ingestmodules.bulkextractor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.ingest.ProcTerminationCode; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * A file ingest module that runs the Bulk Extractor executable with unallocated + * space files as input. + */ +@NbBundle.Messages({ + "SettingsWrong='Process unallocated space only' is checked for this module, but global 'Process Unallocated Space' is not checked. Unallocated space will not be processed.", + "Utilities.cannotCreateOutputDir.message=Unable to create output directory." +}) +final class UnallocatedSpaceIngestModule implements FileIngestModule { + + private static final String TEMP_DIR_NAME = "temp"; // NON-NLS + private static final Logger logger = Logger.getLogger(UnallocatedSpaceIngestModule.class.getName()); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); + private static final Map tempPathsByJob = new ConcurrentHashMap<>(); + private final BulkExtractorIngestJobSettings settings; + private IngestJobContext context; + private Path bulkExtractorPath; + private Path rootOutputDirPath; + + /** + * Constructs a file ingest module that runs the Bulk Extractor executable + * with unallocated space files as input. + * + * @param settings The settings for the ingest module. + */ + UnallocatedSpaceIngestModule(BulkExtractorIngestJobSettings settings) { + this.settings = settings; + } + + /** + * @inheritDoc + */ + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + + // Only process unallocated space files when the user does not want the + // data source to be scanned in its entirety. Scans of an entire disk + // image are performed by a data source ingest module. + if (!this.settings.shouldOnlyProcessUnallocatedSpace()) { + return; + } + + // If the global unallocated space processing setting and the module + // process unallocated space only setting are not in sych, throw an + // exception. Although the result would not be incorrect, it would be + // unfortunate for the user to get an accidental no-op for this module. + if (!this.context.processingUnallocatedSpace()) { + throw new IngestModuleException(Bundle.SettingsWrong()); + } + + this.bulkExtractorPath = Utilities.locateBulkExtractorExecutable(); + this.rootOutputDirPath = Utilities.createOutputDirectoryForDataSource(this.context.getDataSource().getName() + "_" + + this.context.getDataSource().getId()); + + // The first instance of the module for an ingest job creates + // a temp subdirectory as a location for writing unallocated + // space files to disk. + if (UnallocatedSpaceIngestModule.refCounter.incrementAndGet(this.context.getJobId()) == 1) { + try { + Path tempDirPath = Paths.get(this.rootOutputDirPath.toString(), UnallocatedSpaceIngestModule.TEMP_DIR_NAME + Long.toString(this.context.getJobId())); + Files.createDirectory(tempDirPath); + UnallocatedSpaceIngestModule.tempPathsByJob.put(this.context.getJobId(), tempDirPath); + } catch (SecurityException | IOException | UnsupportedOperationException ex) { + throw new IngestModule.IngestModuleException(Bundle.Utilities_cannotCreateOutputDir_message(), ex); + } + } + } + + /** + * @inheritDoc + */ + @Override + public ProcessResult process(AbstractFile file) { + // Only process unallocated space files when the user does not want the + // data source to be scanned in its entirety. Scans of an entire disk + // image are performed by a data source ingest module. + if (!this.settings.shouldOnlyProcessUnallocatedSpace()) { + return ProcessResult.OK; + } + + // Skip everything except unallocated space files. + if (file.getType() != TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { + return ProcessResult.OK; + } + + Path tempFilePath = null; + try { + // Verify initialization succeeded. + if (null == this.bulkExtractorPath) { + UnallocatedSpaceIngestModule.logger.log(Level.SEVERE, "Bulk Extractor unallocated space ingest module called after failed start up"); // NON-NLS + return ProcessResult.ERROR; + } + + // Write the file to disk. + Path tempDirPath = UnallocatedSpaceIngestModule.tempPathsByJob.get(this.context.getJobId()); + tempFilePath = Paths.get(tempDirPath.toString(), file.getName()); + ContentUtils.writeToFile(file, tempFilePath.toFile()); + + // Make an output directory for Bulk Extractor. + Path outputDirPath = Utilities.getOutputSubdirectoryPath(this.rootOutputDirPath, file.getId()); + Files.createDirectories(outputDirPath); + + // Scan the file with Bulk Extractor. + FileIngestModuleProcessTerminator terminator = new FileIngestModuleProcessTerminator(this.context, true); + int exitValue = Utilities.runBulkExtractor(this.bulkExtractorPath, outputDirPath, tempFilePath, terminator); + + if (terminator.getTerminationCode() == ProcTerminationCode.TIME_OUT) { + String msg = NbBundle.getMessage(this.getClass(), "BulkExtractorIngestModule.processTerminated") + file.getName(); // NON-NLS + MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "BulkExtractorIngestModule.moduleError"), msg); // NON-NLS + UnallocatedSpaceIngestModule.logger.log(Level.SEVERE, msg); + FileUtil.deleteDir(new File(outputDirPath.toAbsolutePath().toString())); + return IngestModule.ProcessResult.ERROR; + } + + if (0 != exitValue) { + FileUtil.deleteDir(new File(outputDirPath.toAbsolutePath().toString())); + if (this.context.dataSourceIngestIsCancelled()) { + return ProcessResult.OK; + } else { + UnallocatedSpaceIngestModule.logger.log(Level.SEVERE, "Bulk Extractor returned error exit value = {0} when scanning unallocated space of {1}", new Object[]{exitValue, file.getName()}); // NON-NLS + return ProcessResult.ERROR; + } + } + + if (!Utilities.isDirectoryEmpty(outputDirPath)) { + // Add the output directory to the case as an Autopsy report. + Case.getCurrentCase().addReport(outputDirPath.toAbsolutePath().toString(), Utilities.getModuleName(), Utilities.getReportName(file.getName())); + } + + return ProcessResult.OK; + + } catch (IOException | InterruptedException | TskCoreException ex) { + UnallocatedSpaceIngestModule.logger.log(Level.SEVERE, "Error processing " + file.getName() + " with Bulk Extractor", ex); // NON-NLS + return ProcessResult.ERROR; + } finally { + if (null != tempFilePath && Files.exists(tempFilePath)) { + // Get rid of the unallocated space file. + tempFilePath.toFile().delete(); + } + } + } + + @Override + public void shutDown() { + if (!this.settings.shouldOnlyProcessUnallocatedSpace()) { + return; + } + + if (refCounter.decrementAndGet(this.context.getJobId()) == 0) { + try { + // The last instance of this module for an ingest job deletes + // the temp dir. + Path tempDirPath = UnallocatedSpaceIngestModule.tempPathsByJob.remove(this.context.getJobId()); + FileUtil.deleteDir(new File(tempDirPath.toAbsolutePath().toString())); + } catch (SecurityException ex) { + UnallocatedSpaceIngestModule.logger.log(Level.SEVERE, "Error shutting down Bulk Extractor unallocated space module", ex); // NON-NLS + } + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/Utilities.java b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/Utilities.java new file mode 100755 index 0000000000..bd710b2dc6 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/ingestmodules/bulkextractor/Utilities.java @@ -0,0 +1,223 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.ingestmodules.bulkextractor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.openide.modules.InstalledFileLocator; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.coreutils.ExecUtil.ProcessTerminator; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.ingest.IngestModule; + +/** + * Utility methods for the Bulk Extractor ingest module. + */ +@NbBundle.Messages({ + "Utilities.unsupportedOS.message=Bulk Extractor Module is only supported for Windows platforms.", + "Utilities.missingBulkExtractor.message=Unable to locate Bulk Extractor executable.", + "Utilities.cannotExecuteBulkExtractor.message=Unable to execute Bulk Extractor.", + "# {0} - output directory name", "Utilities.cannotCreateOutputDir.message.with.param=Unable to create output directory: {0}." +}) +final class Utilities { + + private static final String OUTPUT_SUB_DIRECTORY = "BulkExtractor"; // NON-NLS + private static final String REPORT_NAME_BASE = "Bulk Extractor Scan"; // NON-NLS + private static final String BULK_EXTRACTOR_DIR = "bulk_extractor_1_5_3"; // NON-NLS + private static final String BULK_EXTRACTOR_VERSION = "1.5.3"; // NON-NLS + private static final String BULK_EXTRACTOR_32_BIT_DIR = "32-bit"; // NON-NLS + private static final String BULK_EXTRACTOR_64_BIT_DIR = "64-bit"; // NON-NLS + private static final String BULK_EXTRACTOR_WINDOWS_EXE = "bulk_extractor.exe"; // NON-NLS + + /** + * Gets the ingest module name. + * + * @return A name string. + */ + static String getModuleName() { + return NbBundle.getMessage(BulkExtractorIngestModuleFactory.class, "Utilities.moduleName"); + } + + /** + * Gets the version of the module + * @return Version string + */ + static String getVersion() { + return BULK_EXTRACTOR_VERSION; + } + + /** + * Creates a report name based on the name of file presumed to be scanned by + * Bulk Extractor. + * + * @param scannedFileName The name of the file. + * + * @return The report name string. + */ + static String getReportName(String scannedFileName) { + return scannedFileName + " " + Utilities.REPORT_NAME_BASE; + } + + /** + * Creates the output directory for this module for the current case and + * data source, if it does not already exist. + * + * @return The absolute path of the output directory. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + synchronized static Path createOutputDirectoryForDataSource(String subDirName) throws IngestModule.IngestModuleException { + Path path = Paths.get(Case.getCurrentCase().getModuleDirectory(), Utilities.OUTPUT_SUB_DIRECTORY, subDirName); + try { + Files.createDirectories(path); + } catch (FileAlreadyExistsException ex) { + // No worries. + } catch (IOException | SecurityException | UnsupportedOperationException ex) { + throw new IngestModule.IngestModuleException(Bundle.Utilities_cannotCreateOutputDir_message_with_param(path.toString()), ex); + } + return path; + } + + /** + * Creates a path for an output subdirectory to contain the results of + * scanning a file with Bulk Extractor. + * + * @param rootOutputDirPath The root output directory path + * @param objectId The object id of the file to be scanned. + * + * @return The path. + */ + static Path getOutputSubdirectoryPath(Path rootOutputDirPath, long objectId) { + StringBuilder nameBuilder = new StringBuilder(Long.toString(objectId)); + nameBuilder.append("_"); // NON-NLS + DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS"); // NON-NLS + Date date = new Date(); + nameBuilder.append(dateFormat.format(date)); + return Paths.get(rootOutputDirPath.toAbsolutePath().toString(), nameBuilder.toString()); + } + + /** + * Locates the Bulk Extractor executable. + * + * @return The path of the executable. + * + * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException + */ + static Path locateBulkExtractorExecutable() throws IngestModule.IngestModuleException { + // Must be running under a Windows operating system. + if (!PlatformUtil.isWindowsOS()) { + throw new IngestModule.IngestModuleException(Bundle.Utilities_unsupportedOS_message()); + } + + // Build the expected path to either the 32-bit or 64-bit version of the + // Bulk Extractor executable. + final File beRoot = InstalledFileLocator.getDefault().locate(Utilities.BULK_EXTRACTOR_DIR, Utilities.class.getPackage().getName(), false); + + Path executablePath; + if (PlatformUtil.is64BitOS()) { + executablePath = Paths.get( + beRoot.getAbsolutePath(), + Utilities.BULK_EXTRACTOR_64_BIT_DIR, + Utilities.BULK_EXTRACTOR_WINDOWS_EXE); + } else { + executablePath = Paths.get( + beRoot.getAbsolutePath(), + Utilities.BULK_EXTRACTOR_32_BIT_DIR, + Utilities.BULK_EXTRACTOR_WINDOWS_EXE); + } + + // Make sure the executable exists at the expected location and that it + // can be run. + File bulkExtractor = executablePath.toFile(); + if (null == bulkExtractor || !bulkExtractor.exists()) { + throw new IngestModule.IngestModuleException(Bundle.Utilities_missingBulkExtractor_message()); + } + if (!bulkExtractor.canExecute()) { + throw new IngestModule.IngestModuleException(Bundle.Utilities_cannotExecuteBulkExtractor_message()); + } + + return executablePath; + } + + /** + * Runs the Bulk Extractor executable. + * + * @param bulkExtractorPath The path to the Bulk Extractor executable. + * @param outputDirPath The path to the module output directory. + * @param inputFilePath The path to the input file. + * @param terminator + * + * @return The exit value of the subprocess used to run Bulk Extractor. + * + * @throws IOException + * @throws InterruptedException + */ + static int runBulkExtractor(Path bulkExtractorPath, Path outputDirPath, Path inputFilePath, ProcessTerminator terminator) throws SecurityException, IOException, InterruptedException { + List commandLine = new ArrayList<>(); + commandLine.add(bulkExtractorPath.toAbsolutePath().toString()); + commandLine.add("-e"); + commandLine.add("facebook"); + commandLine.add("-o"); + commandLine.add(outputDirPath.toAbsolutePath().toString()); + commandLine.add(inputFilePath.toAbsolutePath().toString()); + ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + + // redirect BE stdout and stderr to txt files + Path logFileName = Paths.get(outputDirPath.getParent().toString(), outputDirPath.getFileName().toString() + "_out.txt"); + File logFile = new File(logFileName.toString()); + Path errFileName = Paths.get(outputDirPath.getParent().toString(), outputDirPath.getFileName().toString() + "_err.txt"); + File errFile = new File(errFileName.toString()); + processBuilder.redirectError(ProcessBuilder.Redirect.appendTo(errFile)); + processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)); + + return ExecUtil.execute(processBuilder, terminator); + } + + /** + * Determines whether or not a directory is empty. + * + * @param directoryPath The path to the directory to inspect. + * + * @return True if the directory is empty, false otherwise. + * + * @throws IllegalArgumentException + * @throws IOException + */ + static boolean isDirectoryEmpty(final Path directoryPath) throws IllegalArgumentException, IOException { + if (!Files.isDirectory(directoryPath)) { + throw new IllegalArgumentException("The directoryPath argument must be a directory path"); // NON-NLS + } + try (DirectoryStream dirStream = Files.newDirectoryStream(directoryPath)) { + return !dirStream.iterator().hasNext(); + } + } + +}