Merge pull request #5439 from markmckinnon/5679-Allow-Plaso-to-more-silently-fail-for-non-images

5679-Allow-Plaso-to-more-silently-fail-for-non-images
This commit is contained in:
Richard Cordovano 2019-12-03 11:22:45 -05:00 committed by GitHub
commit 5f58ac294b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -85,7 +85,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
private static final int LOG2TIMELINE_WORKERS = 2; private static final int LOG2TIMELINE_WORKERS = 2;
private static final long TERMINATION_CHECK_INTERVAL = 5; private static final long TERMINATION_CHECK_INTERVAL = 5;
private static final TimeUnit TERMINATION_CHECK_INTERVAL_UNITS = TimeUnit.SECONDS; private static final TimeUnit TERMINATION_CHECK_INTERVAL_UNITS = TimeUnit.SECONDS;
private File log2TimeLineExecutable; private File log2TimeLineExecutable;
private File psortExecutable; private File psortExecutable;
@ -103,8 +103,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
@NbBundle.Messages({ @NbBundle.Messages({
"PlasoIngestModule.executable.not.found=Plaso Executable Not Found.", "PlasoIngestModule.executable.not.found=Plaso Executable Not Found.",
"PlasoIngestModule.requires.windows=Plaso module requires windows.", "PlasoIngestModule.requires.windows=Plaso module requires windows."})
"PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image."})
@Override @Override
public void startUp(IngestJobContext context) throws IngestModuleException { public void startUp(IngestJobContext context) throws IngestModuleException {
this.context = context; this.context = context;
@ -121,11 +120,6 @@ public class PlasoIngestModule implements DataSourceIngestModule {
throw new IngestModuleException(Bundle.PlasoIngestModule_executable_not_found(), exception); 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({ @NbBundle.Messages({
@ -138,80 +132,90 @@ public class PlasoIngestModule implements DataSourceIngestModule {
"PlasoIngestModule.psort.cancelled=psort run was canceled", "PlasoIngestModule.psort.cancelled=psort run was canceled",
"PlasoIngestModule.bad.imageFile=Cannot find image file name and path", "PlasoIngestModule.bad.imageFile=Cannot find image file name and path",
"PlasoIngestModule.completed=Plaso Processing Completed", "PlasoIngestModule.completed=Plaso Processing Completed",
"PlasoIngestModule.has.run=Plaso Plugin has been run.", "PlasoIngestModule.has.run=Plaso",
"PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete."}) "PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete.",
"PlasoIngestModule.dataSource.not.an.image=Skipping non-disk image datasource"})
@Override @Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) { public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
assert dataSource.equals(image);
statusHelper.switchToDeterminate(100); if (!(dataSource instanceof Image)) {
currentCase = Case.getCurrentCase(); IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
fileManager = currentCase.getServices().getFileManager(); Bundle.PlasoIngestModule_has_run(),
Bundle.PlasoIngestModule_dataSource_not_an_image());
IngestServices.getInstance().postMessage(message);
return ProcessResult.OK;
} else {
image = (Image) dataSource;
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS statusHelper.switchToDeterminate(100);
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime); currentCase = Case.getCurrentCase();
try { fileManager = currentCase.getServices().getFileManager();
Files.createDirectories(moduleOutputPath);
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
return ProcessResult.ERROR;
}
// Run log2timeline String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime);
statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0); try {
ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image); Files.createDirectories(moduleOutputPath);
try { } catch (IOException ex) {
Process log2TimeLineProcess = log2TimeLineCommand.start(); logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
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, TERMINATION_CHECK_INTERVAL, TERMINATION_CHECK_INTERVAL_UNITS, 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; return ProcessResult.ERROR;
} }
// sort the output // Run log2timeline
statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33); logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS
ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath); statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
int result = ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context)); ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image);
if (result != 0) { try {
logger.log(Level.SEVERE, String.format("Error running Psort, error code returned %d", result)); //NON-NLS Process log2TimeLineProcess = log2TimeLineCommand.start();
MessageNotifyUtil.Notify.error(MODULE_NAME, Bundle.PlasoIngestModule_psort_fail()); try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
return ProcessResult.ERROR; L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath);
} new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS
ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, TERMINATION_CHECK_INTERVAL, TERMINATION_CHECK_INTERVAL_UNITS, new DataSourceIngestModuleProcessTerminator(context));
statusReader.cancel();
}
if (context.dataSourceIngestIsCancelled()) { if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "psort run was canceled"); //NON-NLS logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS
return ProcessResult.OK; return ProcessResult.OK;
} }
Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
if (Files.notExists(plasoFile)) { logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS
logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS return ProcessResult.ERROR;
}
// sort the output
statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33);
ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath);
int result = ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context));
if (result != 0) {
logger.log(Level.SEVERE, String.format("Error running Psort, error code returned %d", result)); //NON-NLS
MessageNotifyUtil.Notify.error(MODULE_NAME, Bundle.PlasoIngestModule_psort_fail());
return ProcessResult.ERROR;
}
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; return ProcessResult.ERROR;
} }
// parse the output and make artifacts IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
createPlasoArtifacts(plasoFile.toString(), statusHelper); Bundle.PlasoIngestModule_has_run(),
Bundle.PlasoIngestModule_completed());
} catch (IOException ex) { IngestServices.getInstance().postMessage(message);
logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS return ProcessResult.OK;
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) { private ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image) {
@ -240,8 +244,10 @@ public class PlasoIngestModule implements DataSourceIngestModule {
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) { static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
ProcessBuilder processBuilder = new ProcessBuilder(commandLine); ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
/* Add an environment variable to force log2timeline/psort to run with /*
* the same permissions Autopsy uses. */ * Add an environment variable to force log2timeline/psort to run with
* the same permissions Autopsy uses.
*/
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
return processBuilder; return processBuilder;
} }
@ -277,31 +283,30 @@ public class PlasoIngestModule implements DataSourceIngestModule {
"PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ", "PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ",
"# {0} - file that events are from", "# {0} - file that events are from",
"PlasoIngestModule.artifact.progress=Adding events to case: {0}", "PlasoIngestModule.artifact.progress=Adding events to case: {0}",
"PlasoIngestModule.info.empty.database=Plaso database was empty.", "PlasoIngestModule.info.empty.database=Plaso database was empty.",})
})
private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) { private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) {
Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard(); Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
String sqlStatement = "SELECT substr(filename,1) AS filename, " String sqlStatement = "SELECT substr(filename,1) AS filename, "
+ " strftime('%s', datetime) AS epoch_date, " + " strftime('%s', datetime) AS epoch_date, "
+ " description, " + " description, "
+ " source, " + " source, "
+ " type, " + " type, "
+ " sourcetype " + " sourcetype "
+ " FROM log2timeline " + " FROM log2timeline "
+ " WHERE source NOT IN ('FILE', " + " WHERE source NOT IN ('FILE', "
+ " 'WEBHIST') " // bad dates and duplicates with what we have. + " 'WEBHIST') " // bad dates and duplicates with what we have.
+ " AND sourcetype NOT IN ('UNKNOWN', " + " AND sourcetype NOT IN ('UNKNOWN', "
+ " 'PE Import Time');"; // lots of bad dates //NON-NLS + " 'PE Import Time');"; // lots of bad dates //NON-NLS
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) { ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
boolean dbHasData = false; boolean dbHasData = false;
while (resultSet.next()) { while (resultSet.next()) {
dbHasData = true; dbHasData = true;
if (context.dataSourceIngestIsCancelled()) { if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS
return; return;
@ -314,20 +319,20 @@ public class PlasoIngestModule implements DataSourceIngestModule {
logger.log(Level.INFO, "File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);//NON-NLS 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; resolvedFile = image;
} }
String description = resultSet.getString("description"); String description = resultSet.getString("description");
TimelineEventType eventType = findEventSubtype(currentFileName, resultSet); TimelineEventType eventType = findEventSubtype(currentFileName, resultSet);
// If the description is empty use the event type display name // If the description is empty use the event type display name
// as the description. // as the description.
if ( description == null || description.isEmpty() ) { if (description == null || description.isEmpty()) {
if (eventType != TimelineEventType.OTHER) { if (eventType != TimelineEventType.OTHER) {
description = eventType.getDisplayName(); description = eventType.getDisplayName();
} else { } else {
continue; continue;
} }
} }
Collection<BlackboardAttribute> bbattributes = Arrays.asList( Collection<BlackboardAttribute> bbattributes = Arrays.asList(
new BlackboardAttribute( new BlackboardAttribute(
TSK_DATETIME, MODULE_NAME, TSK_DATETIME, MODULE_NAME,
@ -338,14 +343,16 @@ public class PlasoIngestModule implements DataSourceIngestModule {
new BlackboardAttribute( new BlackboardAttribute(
TSK_TL_EVENT_TYPE, MODULE_NAME, TSK_TL_EVENT_TYPE, MODULE_NAME,
eventType.getTypeID())); eventType.getTypeID()));
try { try {
BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT); BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT);
bbart.addAttributes(bbattributes); bbart.addAttributes(bbattributes);
try { try {
/* Post the artifact which will index the artifact for /*
* Post the artifact which will index the artifact for
* keyword search, and fire an event to notify UI of * keyword search, and fire an event to notify UI of
* this new artifact */ * this new artifact
*/
blackboard.postArtifact(bbart, MODULE_NAME); blackboard.postArtifact(bbart, MODULE_NAME);
} catch (BlackboardException ex) { } catch (BlackboardException ex) {
logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
@ -354,12 +361,12 @@ public class PlasoIngestModule implements DataSourceIngestModule {
logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
} }
} }
// Check if there is data the db // Check if there is data the db
if( !dbHasData ) { if (!dbHasData) {
logger.log(Level.INFO, String.format("PlasoDB was empty: %s", plasoDb)); logger.log(Level.INFO, String.format("PlasoDB was empty: %s", plasoDb));
MessageNotifyUtil.Notify.info(MODULE_NAME, Bundle.PlasoIngestModule_info_empty_database()); MessageNotifyUtil.Notify.info(MODULE_NAME, Bundle.PlasoIngestModule_info_empty_database());
} }
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
} }
@ -377,8 +384,8 @@ public class PlasoIngestModule implements DataSourceIngestModule {
// check the cached file // check the cached file
//TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it? //TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it?
if (previousFile != null if (previousFile != null
&& previousFile.getName().equalsIgnoreCase(fileName) && previousFile.getName().equalsIgnoreCase(fileName)
&& previousFile.getParentPath().equalsIgnoreCase(filePath)) { && previousFile.getParentPath().equalsIgnoreCase(filePath)) {
return previousFile; return previousFile;
} }
@ -416,7 +423,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
switch (row.getString("source")) { switch (row.getString("source")) {
case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case... case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case...
if (fileName.toLowerCase().contains(COOKIE) if (fileName.toLowerCase().contains(COOKIE)
|| row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS || row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS
return TimelineEventType.WEB_COOKIE; return TimelineEventType.WEB_COOKIE;
} else { } else {