diff --git a/Core/build.xml b/Core/build.xml index bfde1a520b..f2b984763d 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -39,7 +39,11 @@ - + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java index 0a76b2a7fa..9a211c57a0 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java @@ -196,6 +196,53 @@ public final class ExecUtil { } return process.exitValue(); } + + /** + * Wait for the given process to finish, using the given ProcessTerminator. + * + * @param command The command that was used to start the process. Used + * only for logging purposes. + * @param process The process to wait for. + * @param terminator The ProcessTerminator used to determine if the process + * should be killed. + * + * @returnthe exit value of the process + * + * @throws SecurityException if a security manager exists and vetoes any + * aspect of running the process. + * @throws IOException if an I/o error occurs. + */ + public static int waitForTermination(String command, Process process, ProcessTerminator terminator) throws SecurityException, IOException { + return ExecUtil.waitForTermination(command, process, ExecUtil.DEFAULT_TIMEOUT, ExecUtil.DEFAULT_TIMEOUT_UNITS, terminator); + } + + private static int waitForTermination(String command, Process process, long timeOut, TimeUnit units, ProcessTerminator terminator) throws SecurityException, IOException { + try { + do { + process.waitFor(timeOut, units); + if (process.isAlive() && terminator.shouldTerminateProcess()) { + killProcess(process); + try { + process.waitFor(); //waiting to help ensure process is shutdown before calling interrupt() or returning + } catch (InterruptedException exx) { + Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, String.format("Wait for process termination following killProcess was interrupted for command %s", command)); + } + } + } while (process.isAlive()); + } catch (InterruptedException ex) { + if (process.isAlive()) { + killProcess(process); + } + try { + process.waitFor(); //waiting to help ensure process is shutdown before calling interrupt() or returning + } catch (InterruptedException exx) { + Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, String.format("Wait for process termination following killProcess was interrupted for command %s", command)); + } + Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, "Thread interrupted while running {0}", command); // NON-NLS + Thread.currentThread().interrupt(); + } + return process.exitValue(); + } /** * Kills a process and its children diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties new file mode 100755 index 0000000000..9fe1026713 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties @@ -0,0 +1,3 @@ +PlasoModuleSettingsPanel.winRegCheckBox.text=winreg: Parser for Windows NT Registry (REGF) files. +PlasoModuleSettingsPanel.peCheckBox.text=pe: Parser for Portable Executable (PE) files. +PlasoModuleSettingsPanel.plasoParserInfoTextArea.text=All plaso parsers except chrome_cache and the ones listed below are run. chrome_cache duplicates data collected by the RecentActivity module. The parsers below add significantly to the processing time and should only be enabled if the events they produce are needed. diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties-MERGED new file mode 100755 index 0000000000..e29304b633 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties-MERGED @@ -0,0 +1,25 @@ +# {0} - file that events are from +PlasoIngestModule.artifact.progress=Adding events to case: {0} +PlasoIngestModule.bad.imageFile=Cannot find image file name and path +PlasoIngestModule.completed=Plaso Processing Completed +PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation +PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image. +PlasoIngestModule.error.creating.output.dir=Error creating Plaso module output directory. +PlasoIngestModule.error.running.log2timeline=Error running log2timeline, see log file. +PlasoIngestModule.error.running.psort=Error running Psort, see log file. +PlasoIngestModule.event.datetime=Event Date Time +PlasoIngestModule.event.description=Event Description +PlasoIngestModule.exception.posting.artifact=Exception Posting artifact. +PlasoIngestModule.executable.not.found=Plaso Executable Not Found. +PlasoIngestModule.has.run=Plaso Plugin has been run. +PlasoIngestModule.log2timeline.cancelled=Log2timeline run was canceled +PlasoIngestModule.psort.cancelled=psort run was canceled +PlasoIngestModule.requires.windows=Plaso module requires windows. +PlasoIngestModule.running.psort=Running Psort +PlasoIngestModule.starting.log2timeline=Starting Log2timeline +PlasoModuleFactory.ingestJobSettings.exception.msg=Expected settings argument to be instanceof PlasoModuleSettings +PlasoModuleFactory_moduleDesc=Runs Plaso against a Data Source. +PlasoModuleFactory_moduleName=Plaso +PlasoModuleSettingsPanel.winRegCheckBox.text=winreg: Parser for Windows NT Registry (REGF) files. +PlasoModuleSettingsPanel.peCheckBox.text=pe: Parser for Portable Executable (PE) files. +PlasoModuleSettingsPanel.plasoParserInfoTextArea.text=All plaso parsers except chrome_cache and the ones listed below are run. chrome_cache duplicates data collected by the RecentActivity module. The parsers below add significantly to the processing time and should only be enabled if the events they produce are needed. diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java new file mode 100755 index 0000000000..5fd4946a09 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java @@ -0,0 +1,458 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018-2019 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.modules.plaso; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import static java.util.Objects.nonNull; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.openide.modules.InstalledFileLocator; +import org.openide.util.Cancellable; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect; +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.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TL_EVENT_TYPE; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.timeline.EventType; + +/** + * Data source ingest module that runs Plaso against the image. + */ +public class PlasoIngestModule implements DataSourceIngestModule { + + private static final Logger logger = Logger.getLogger(PlasoIngestModule.class.getName()); + private static final String MODULE_NAME = PlasoModuleFactory.getModuleName(); + + private static final String PLASO = "plaso"; //NON-NLS + private static final String PLASO64 = "plaso-20180818-amd64";//NON-NLS + private static final String PLASO32 = "plaso-20180818-win32";//NON-NLS + private static final String LOG2TIMELINE_EXECUTABLE = "Log2timeline.exe";//NON-NLS + private static final String PSORT_EXECUTABLE = "psort.exe";//NON-NLS + private static final String COOKIE = "cookie";//NON-NLS + private static final int LOG2TIMELINE_WORKERS = 2; + + private File log2TimeLineExecutable; + private File psortExecutable; + + private final PlasoModuleSettings settings; + private IngestJobContext context; + private Case currentCase; + private FileManager fileManager; + + private Image image; + private AbstractFile previousFile = null; // cache used when looking up files in Autopsy DB + + PlasoIngestModule(PlasoModuleSettings settings) { + this.settings = settings; + } + + @NbBundle.Messages({ + "PlasoIngestModule.executable.not.found=Plaso Executable Not Found.", + "PlasoIngestModule.requires.windows=Plaso module requires windows.", + "PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image."}) + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + + if (false == PlatformUtil.isWindowsOS()) { + throw new IngestModuleException(Bundle.PlasoIngestModule_requires_windows()); + } + + try { + log2TimeLineExecutable = locateExecutable(LOG2TIMELINE_EXECUTABLE); + psortExecutable = locateExecutable(PSORT_EXECUTABLE); + } catch (FileNotFoundException exception) { + logger.log(Level.WARNING, "Plaso executable not found.", exception); //NON-NLS + throw new IngestModuleException(Bundle.PlasoIngestModule_executable_not_found(), exception); + } + + Content dataSource = context.getDataSource(); + if (!(dataSource instanceof Image)) { + throw new IngestModuleException(Bundle.PlasoIngestModule_dataSource_not_an_image()); + } + image = (Image) dataSource; + } + + @NbBundle.Messages({ + "PlasoIngestModule.error.running.log2timeline=Error running log2timeline, see log file.", + "PlasoIngestModule.error.running.psort=Error running Psort, see log file.", + "PlasoIngestModule.error.creating.output.dir=Error creating Plaso module output directory.", + "PlasoIngestModule.starting.log2timeline=Starting Log2timeline", + "PlasoIngestModule.running.psort=Running Psort", + "PlasoIngestModule.log2timeline.cancelled=Log2timeline run was canceled", + "PlasoIngestModule.psort.cancelled=psort run was canceled", + "PlasoIngestModule.bad.imageFile=Cannot find image file name and path", + "PlasoIngestModule.completed=Plaso Processing Completed", + "PlasoIngestModule.has.run=Plaso Plugin has been run."}) + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) { + assert dataSource.equals(image); + + statusHelper.switchToDeterminate(100); + currentCase = Case.getCurrentCase(); + fileManager = currentCase.getServices().getFileManager(); + + String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS + Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime); + try { + Files.createDirectories(moduleOutputPath); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS + return ProcessResult.ERROR; + } + + // Run log2timeline + logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS + statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0); + ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image); + try { + Process log2TimeLineProcess = log2TimeLineCommand.start(); + try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) { + L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath); + new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS + ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, new DataSourceIngestModuleProcessTerminator(context)); + statusReader.cancel(); + } + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS + return ProcessResult.OK; + } + if (Files.notExists(moduleOutputPath.resolve(PLASO))) { + logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS + return ProcessResult.ERROR; + } + + // sort the output + statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33); + ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath); + ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context)); + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "psort run was canceled"); //NON-NLS + return ProcessResult.OK; + } + Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS + if (Files.notExists(plasoFile)) { + logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS + return ProcessResult.ERROR; + } + + // parse the output and make artifacts + createPlasoArtifacts(plasoFile.toString(), statusHelper); + + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS + return ProcessResult.ERROR; + } + + IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, + Bundle.PlasoIngestModule_has_run(), + Bundle.PlasoIngestModule_completed()); + IngestServices.getInstance().postMessage(message); + return ProcessResult.OK; + } + + private ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image) { + //make a csv list of disabled parsers. + String parsersString = settings.getParsers().entrySet().stream() + .filter(entry -> entry.getValue() == false) + .map(entry -> "!" + entry.getKey()) // '!' prepended to parsername disables it. //NON-NLS + .collect(Collectors.joining(","));//NON-NLS + + ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( + "\"" + log2TimeLineExecutable + "\"", //NON-NLS + "--vss-stores", "all", //NON-NLS + "-z", image.getTimeZone(), //NON-NLS + "--partitions", "all", //NON-NLS + "--hasher_file_size_limit", "1", //NON-NLS + "--hashers", "none", //NON-NLS + "--parsers", "\"" + parsersString + "\"",//NON-NLS + "--no_dependencies_check", //NON-NLS + "--workers", String.valueOf(LOG2TIMELINE_WORKERS),//NON-NLS + moduleOutputPath.resolve(PLASO).toString(), + image.getPaths()[0] + ); + processBuilder.redirectError(moduleOutputPath.resolve("log2timeline_err.txt").toFile()); //NON-NLS + return processBuilder; + } + + static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) { + ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + /* Add an environment variable to force log2timeline/psort to run with + * the same permissions Autopsy uses. */ + processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS + return processBuilder; + } + + private ProcessBuilder buildPsortCommand(Path moduleOutputPath) { + ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( + "\"" + psortExecutable + "\"", //NON-NLS + "-o", "4n6time_sqlite", //NON-NLS + "-w", moduleOutputPath.resolve("plasodb.db3").toString(), //NON-NLS + moduleOutputPath.resolve(PLASO).toString() + ); + + processBuilder.redirectOutput(moduleOutputPath.resolve("psort_output.txt").toFile()); //NON-NLS + processBuilder.redirectError(moduleOutputPath.resolve("psort_err.txt").toFile()); //NON-NLS + return processBuilder; + } + + private static File locateExecutable(String executableName) throws FileNotFoundException { + String architectureFolder = PlatformUtil.is64BitOS() ? PLASO64 : PLASO32; + String executableToFindName = Paths.get(PLASO, architectureFolder, executableName).toString(); + + File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PlasoIngestModule.class.getPackage().getName(), false); + if (null == exeFile || exeFile.canExecute() == false) { + throw new FileNotFoundException(executableName + " executable not found."); + } + return exeFile; + } + + @NbBundle.Messages({ + "PlasoIngestModule.exception.posting.artifact=Exception Posting artifact.", + "PlasoIngestModule.event.datetime=Event Date Time", + "PlasoIngestModule.event.description=Event Description", + "PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ", + "# {0} - file that events are from", + "PlasoIngestModule.artifact.progress=Adding events to case: {0}"}) + private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) { + Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard(); + + String sqlStatement = "SELECT substr(filename,1) AS filename, " + + " strftime('%s', datetime) AS epoch_date, " + + " description, " + + " source, " + + " type, " + + " sourcetype " + + " FROM log2timeline " + + " WHERE source NOT IN ('FILE', " + + " 'WEBHIST') " // bad dates and duplicates with what we have. + + " AND sourcetype NOT IN ('UNKNOWN', " + + " 'PE Import Time');"; // lots of bad dates //NON-NLS + SQLiteDBConnect tempdbconnect = null; + ResultSet resultSet = null; + try { + tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS + resultSet = tempdbconnect.executeQry(sqlStatement); + while (resultSet.next()) { + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS + return; + } + + String currentFileName = resultSet.getString("filename"); //NON-NLS + statusHelper.progress(Bundle.PlasoIngestModule_artifact_progress(currentFileName), 66); + Content resolvedFile = getAbstractFile(currentFileName); + if (resolvedFile == null) { + logger.log(Level.INFO, "File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);//NON-NLS + resolvedFile = image; + } + + Collection bbattributes = Arrays.asList( + new BlackboardAttribute( + TSK_DATETIME, MODULE_NAME, + resultSet.getLong("epoch_date")), //NON-NLS + new BlackboardAttribute( + TSK_DESCRIPTION, MODULE_NAME, + resultSet.getString("description")),//NON-NLS + new BlackboardAttribute( + TSK_TL_EVENT_TYPE, MODULE_NAME, + findEventSubtype(currentFileName, resultSet))); + + try { + BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT); + bbart.addAttributes(bbattributes); + try { + /* Post the artifact which will index the artifact for + * keyword search, and fire an event to notify UI of + * this new artifact */ + blackboard.postArtifact(bbart, MODULE_NAME); + } catch (BlackboardException ex) { + logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS + } + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS + } finally { + if(resultSet != null) { + try { + resultSet.close(); + } catch (SQLException ex) { + logger.log(Level.WARNING, "Unable to close ResultSet", ex); + } + } + + if(tempdbconnect != null) { + tempdbconnect.closeConnection(); + } + } + } + + private AbstractFile getAbstractFile(String file) { + + Path path = Paths.get(file); + String fileName = path.getFileName().toString(); + String filePath = path.getParent().toString().replaceAll("\\\\", "/");//NON-NLS + if (filePath.endsWith("/") == false) {//NON-NLS + filePath += "/";//NON-NLS + } + + // check the cached file + //TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it? + if (previousFile != null + && previousFile.getName().equalsIgnoreCase(fileName) + && previousFile.getParentPath().equalsIgnoreCase(filePath)) { + return previousFile; + + } + try { + List abstractFiles = fileManager.findFiles(fileName, filePath); + if (abstractFiles.size() == 1) {// TODO: why do we bother with this check. also we don't cache the file... + return abstractFiles.get(0); + } + for (AbstractFile resolvedFile : abstractFiles) { + // double check its an exact match + if (filePath.equalsIgnoreCase(resolvedFile.getParentPath())) { + // cache it for next time + previousFile = resolvedFile; + return resolvedFile; + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Exception finding file.", ex); + } + return null; + } + + /** + * Determine the event_type_id of the event from the plaso information. + * + * @param fileName The name of the file this event is from. + * @param row The row returned from the log2timeline table of th eplaso + * output. + * + * @return the event_type_id of the EventType of the given event. + * + * @throws SQLException + */ + private long findEventSubtype(String fileName, ResultSet row) throws SQLException { + switch (row.getString("source")) { + case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case... + if (fileName.toLowerCase().contains(COOKIE) + || row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS + return EventType.WEB_COOKIE.getTypeID(); + } else { + return EventType.WEB_HISTORY.getTypeID(); + } + case "EVT": + case "LOG": + return EventType.LOG_ENTRY.getTypeID(); + case "REG": + switch (row.getString("sourcetype").toLowerCase()) {//NON-NLS + case "unknown : usb entries": + case "unknown : usbstor entries": + return EventType.DEVICES_ATTACHED.getTypeID(); + default: + return EventType.REGISTRY.getTypeID(); + } + default: + return EventType.OTHER.getTypeID(); + } + } + + /** + * Runs in a thread and reads the output of log2timeline. It redirectes the + * output both to a log file, and to the status message of the Plaso ingest + * module progress bar. + */ + private static class L2TStatusProcessor implements Runnable, Cancellable { + + private final BufferedReader log2TimeLineOutpout; + private final DataSourceIngestModuleProgress statusHelper; + volatile private boolean cancelled = false; + private final Path outputPath; + + private L2TStatusProcessor(BufferedReader log2TimeLineOutpout, DataSourceIngestModuleProgress statusHelper, Path outputPath) throws IOException { + this.log2TimeLineOutpout = log2TimeLineOutpout; + this.statusHelper = statusHelper; + this.outputPath = outputPath; + } + + @Override + public void run() { + try (BufferedWriter writer = Files.newBufferedWriter(outputPath.resolve("log2timeline_output.txt"));) {//NON-NLS + String line = log2TimeLineOutpout.readLine(); + while (cancelled == false && nonNull(line)) { + statusHelper.progress(line); + writer.write(line); + writer.newLine(); + line = log2TimeLineOutpout.readLine(); + } + writer.flush(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error reading log2timeline output stream.", ex);//NON-NLS + } + } + + @Override + public boolean cancel() { + cancelled = true; + return true; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleFactory.java new file mode 100755 index 0000000000..4e082a0e6e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleFactory.java @@ -0,0 +1,112 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018-2019 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.modules.plaso; + +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.IngestModuleGlobalSettingsPanel; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * A factory that creates data source ingest modules that run Plaso against an + * image and saves the storage file to module output. + */ +@ServiceProvider(service = IngestModuleFactory.class) +@NbBundle.Messages({"PlasoModuleFactory.ingestJobSettings.exception.msg=Expected settings argument to be instanceof PlasoModuleSettings"}) +public class PlasoModuleFactory implements IngestModuleFactory { + + @NbBundle.Messages({"PlasoModuleFactory_moduleName=Plaso"}) + static String getModuleName() { + return Bundle.PlasoModuleFactory_moduleName(); + } + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + @NbBundle.Messages({"PlasoModuleFactory_moduleDesc=Runs Plaso against a Data Source."}) + @Override + public String getModuleDescription() { + return Bundle.PlasoModuleFactory_moduleDesc(); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean isDataSourceIngestModuleFactory() { + return true; + } + + @Override + public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { + assert settings instanceof PlasoModuleSettings; + if (settings instanceof PlasoModuleSettings) { + return new PlasoIngestModule((PlasoModuleSettings) settings); + } + throw new IllegalArgumentException(Bundle.PlasoModuleFactory_ingestJobSettings_exception_msg()); + } + + @Override + public boolean hasGlobalSettingsPanel() { + return false; + } + + @Override + public IngestModuleGlobalSettingsPanel getGlobalSettingsPanel() { + throw new UnsupportedOperationException(); + } + + @Override + public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { + return new PlasoModuleSettings(); + } + + @Override + public boolean hasIngestJobSettingsPanel() { + return true; + } + + @Override + public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { + assert settings instanceof PlasoModuleSettings; + if (settings instanceof PlasoModuleSettings) { + return new PlasoModuleSettingsPanel((PlasoModuleSettings) settings); + } + throw new IllegalArgumentException(Bundle.PlasoModuleFactory_ingestJobSettings_exception_msg()); + } + + @Override + public boolean isFileIngestModuleFactory() { + return false; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + throw new UnsupportedOperationException(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettings.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettings.java new file mode 100755 index 0000000000..2730f4365f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettings.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.modules.plaso; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * Settings for the Plaso Ingest Module. + */ +public class PlasoModuleSettings implements IngestModuleIngestJobSettings { + + private static final long serialVersionUID = 1L; + + /** Map from parser name (or match pattern) to its enabled state. */ + final Map parsers = new HashMap<>(); + + /** + * Get an immutable map from parser name to its enabled state. Parsers + * mapped to true or with no entry will be enabled. Parsers mapped to false, + * will be disabled. + */ + Map getParsers() { + return ImmutableMap.copyOf(parsers); + } + + /** + * Constructor. The PlasoModuleSettings will have the default parsers + * (winreg, pe, chrome, firefox, internet explorer) disabled. + */ + public PlasoModuleSettings() { + parsers.put("winreg", false); + parsers.put("pe", false); + + //chrome + parsers.put("chrome_preferences", false); + parsers.put("chrome_cache", false); + parsers.put("chrome_27_history", false); + parsers.put("chrome_8_history", false); + parsers.put("chrome_cookies", false); + parsers.put("chrome_extension_activity", false); + + //firefox + parsers.put("firefox_cache", false); + parsers.put("firefox_cache2", false); + parsers.put("firefox_cookies", false); + parsers.put("firefox_downloads", false); + parsers.put("firefox_history", false); + + //Internet Explorer + parsers.put("msiecf", false); + parsers.put("msie_webcache", false); + } + + /** + * Gets the serialization version number. + * + * @return A serialization version number. + */ + @Override + public long getVersionNumber() { + return serialVersionUID; + } + + /** + * Set the given parser enabled/disabled + * + * @param parserName The name of the parser to enable/disable + * @param selected The new state (enabled/disabled) for the given parser. + */ + void setParserEnabled(String parserName, boolean selected) { + parsers.put(parserName, selected); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.form new file mode 100755 index 0000000000..a7da26d3e5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.form @@ -0,0 +1,84 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.java new file mode 100755 index 0000000000..01937ebc2a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.java @@ -0,0 +1,115 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.modules.plaso; + +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * Settings panel for the PlasoIngestModule. + */ +public class PlasoModuleSettingsPanel extends IngestModuleIngestJobSettingsPanel { + + private final PlasoModuleSettings settings; + + public PlasoModuleSettingsPanel(PlasoModuleSettings settings) { + this.settings = settings; + initComponents(); + } + + /** 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() { + + winRegCheckBox = new javax.swing.JCheckBox(); + peCheckBox = new javax.swing.JCheckBox(); + plasoParserInfoTextArea = new javax.swing.JTextArea(); + + org.openide.awt.Mnemonics.setLocalizedText(winRegCheckBox, org.openide.util.NbBundle.getMessage(PlasoModuleSettingsPanel.class, "PlasoModuleSettingsPanel.winRegCheckBox.text")); // NOI18N + winRegCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + winRegCheckBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(peCheckBox, org.openide.util.NbBundle.getMessage(PlasoModuleSettingsPanel.class, "PlasoModuleSettingsPanel.peCheckBox.text")); // NOI18N + peCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + peCheckBoxActionPerformed(evt); + } + }); + + plasoParserInfoTextArea.setEditable(false); + plasoParserInfoTextArea.setBackground(javax.swing.UIManager.getDefaults().getColor("Panel.background")); + plasoParserInfoTextArea.setColumns(20); + plasoParserInfoTextArea.setLineWrap(true); + plasoParserInfoTextArea.setRows(5); + plasoParserInfoTextArea.setText(org.openide.util.NbBundle.getMessage(PlasoModuleSettingsPanel.class, "PlasoModuleSettingsPanel.plasoParserInfoTextArea.text")); // NOI18N + plasoParserInfoTextArea.setWrapStyleWord(true); + plasoParserInfoTextArea.setBorder(null); + + 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() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(plasoParserInfoTextArea) + .addComponent(peCheckBox) + .addComponent(winRegCheckBox)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(plasoParserInfoTextArea, javax.swing.GroupLayout.DEFAULT_SIZE, 188, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addComponent(winRegCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(peCheckBox) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void winRegCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_winRegCheckBoxActionPerformed + settings.setParserEnabled("winreg", winRegCheckBox.isSelected()); + }//GEN-LAST:event_winRegCheckBoxActionPerformed + + private void peCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_peCheckBoxActionPerformed + settings.setParserEnabled("pe", peCheckBox.isSelected()); + }//GEN-LAST:event_peCheckBoxActionPerformed + + @Override + public IngestModuleIngestJobSettings getSettings() { + return settings; + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox peCheckBox; + private javax.swing.JTextArea plasoParserInfoTextArea; + private javax.swing.JCheckBox winRegCheckBox; + // End of variables declaration//GEN-END:variables +}