19 package org.sleuthkit.autopsy.modules.plaso;
21 import java.io.BufferedReader;
22 import java.io.BufferedWriter;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.sql.ResultSet;
31 import java.sql.SQLException;
32 import java.text.SimpleDateFormat;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.Locale;
37 import static java.util.Objects.nonNull;
38 import java.util.concurrent.TimeUnit;
39 import java.util.logging.Level;
40 import java.util.stream.Collectors;
41 import org.openide.modules.InstalledFileLocator;
42 import org.openide.util.Cancellable;
43 import org.openide.util.NbBundle;
59 import org.
sleuthkit.datamodel.Blackboard.BlackboardException;
61 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT;
63 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME;
64 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
65 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TL_EVENT_TYPE;
79 private static final String
PLASO =
"plaso";
80 private static final String
PLASO64 =
"plaso-20180818-amd64";
81 private static final String
PLASO32 =
"plaso-20180818-win32";
84 private static final String
COOKIE =
"cookie";
105 "PlasoIngestModule.executable.not.found=Plaso Executable Not Found.",
106 "PlasoIngestModule.requires.windows=Plaso module requires windows."})
118 }
catch (FileNotFoundException exception) {
119 logger.log(Level.WARNING,
"Plaso executable not found.", exception);
126 "PlasoIngestModule.error.running.log2timeline=Error running log2timeline, see log file.",
127 "PlasoIngestModule.error.running.psort=Error running Psort, see log file.",
128 "PlasoIngestModule.error.creating.output.dir=Error creating Plaso module output directory.",
129 "PlasoIngestModule.starting.log2timeline=Starting Log2timeline",
130 "PlasoIngestModule.running.psort=Running Psort",
131 "PlasoIngestModule.log2timeline.cancelled=Log2timeline run was canceled",
132 "PlasoIngestModule.psort.cancelled=psort run was canceled",
133 "PlasoIngestModule.bad.imageFile=Cannot find image file name and path",
134 "PlasoIngestModule.completed=Plaso Processing Completed",
135 "PlasoIngestModule.has.run=Plaso",
136 "PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete.",
137 "PlasoIngestModule.dataSource.not.an.image=Skipping non-disk image datasource"})
141 if (!(dataSource instanceof Image)) {
143 Bundle.PlasoIngestModule_has_run(),
144 Bundle.PlasoIngestModule_dataSource_not_an_image());
148 image = (Image) dataSource;
154 String currentTime =
new SimpleDateFormat(
"yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());
157 Files.createDirectories(moduleOutputPath);
158 }
catch (IOException ex) {
159 logger.log(Level.SEVERE,
"Error creating Plaso module output directory.", ex);
164 logger.log(Level.INFO,
"Starting Plaso Run.");
165 statusHelper.
progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
168 Process log2TimeLineProcess = log2TimeLineCommand.start();
169 try (BufferedReader log2TimeLineOutpout =
new BufferedReader(
new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
171 new Thread(statusReader,
"log2timeline status reader").start();
177 logger.log(Level.INFO,
"Log2timeline run was canceled");
180 if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
181 logger.log(Level.WARNING,
"Error running log2timeline: there was no storage file.");
186 statusHelper.
progress(Bundle.PlasoIngestModule_running_psort(), 33);
190 logger.log(Level.SEVERE, String.format(
"Error running Psort, error code returned %d", result));
196 logger.log(Level.INFO,
"psort run was canceled");
199 Path plasoFile = moduleOutputPath.resolve(
"plasodb.db3");
200 if (Files.notExists(plasoFile)) {
201 logger.log(Level.SEVERE,
"Error running Psort: there was no sqlite db file.");
208 }
catch (IOException ex) {
209 logger.log(Level.SEVERE,
"Error running Plaso.", ex);
214 Bundle.PlasoIngestModule_has_run(),
215 Bundle.PlasoIngestModule_completed());
223 String parsersString = settings.getParsers().entrySet().stream()
224 .filter(entry -> entry.getValue() ==
false)
225 .map(entry ->
"!" + entry.getKey())
226 .collect(Collectors.joining(
","));
229 "\"" + log2TimeLineExecutable +
"\"",
230 "--vss-stores",
"all",
231 "-z", image.getTimeZone(),
232 "--partitions",
"all",
233 "--hasher_file_size_limit",
"1",
235 "--parsers",
"\"" + parsersString +
"\"",
236 "--no_dependencies_check",
237 "--workers", String.valueOf(LOG2TIMELINE_WORKERS),
238 moduleOutputPath.resolve(PLASO).toString(),
241 processBuilder.redirectError(moduleOutputPath.resolve(
"log2timeline_err.txt").toFile());
242 return processBuilder;
246 ProcessBuilder processBuilder =
new ProcessBuilder(commandLine);
251 processBuilder.environment().put(
"__COMPAT_LAYER",
"RunAsInvoker");
252 return processBuilder;
257 "\"" + psortExecutable +
"\"",
258 "-o",
"4n6time_sqlite",
259 "-w", moduleOutputPath.resolve(
"plasodb.db3").toString(),
260 moduleOutputPath.resolve(PLASO).toString()
263 processBuilder.redirectOutput(moduleOutputPath.resolve(
"psort_output.txt").toFile());
264 processBuilder.redirectError(moduleOutputPath.resolve(
"psort_err.txt").toFile());
265 return processBuilder;
270 String executableToFindName = Paths.get(PLASO, architectureFolder, executableName).toString();
272 File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName,
PlasoIngestModule.class.getPackage().getName(),
false);
273 if (null == exeFile || exeFile.canExecute() ==
false) {
274 throw new FileNotFoundException(executableName +
" executable not found.");
280 "PlasoIngestModule.exception.posting.artifact=Exception Posting artifact.",
281 "PlasoIngestModule.event.datetime=Event Date Time",
282 "PlasoIngestModule.event.description=Event Description",
283 "PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ",
284 "# {0} - file that events are from",
285 "PlasoIngestModule.artifact.progress=Adding events to case: {0}",
286 "PlasoIngestModule.info.empty.database=Plaso database was empty.",})
290 String sqlStatement =
"SELECT substr(filename,1) AS filename, "
291 +
" strftime('%s', datetime) AS epoch_date, "
296 +
" FROM log2timeline "
297 +
" WHERE source NOT IN ('FILE', "
299 +
" AND sourcetype NOT IN ('UNKNOWN', "
300 +
" 'PE Import Time');";
303 ResultSet resultSet = tempdbconnect.
executeQry(sqlStatement)) {
305 boolean dbHasData =
false;
307 while (resultSet.next()) {
311 logger.log(Level.INFO,
"Cancelled Plaso Artifact Creation.");
315 String currentFileName = resultSet.getString(
"filename");
316 statusHelper.
progress(Bundle.PlasoIngestModule_artifact_progress(currentFileName), 66);
318 if (resolvedFile == null) {
319 logger.log(Level.INFO,
"File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);
320 resolvedFile =
image;
323 String description = resultSet.getString(
"description");
328 if (description == null || description.isEmpty()) {
329 if (eventType != TimelineEventType.OTHER) {
330 description = eventType.getDisplayName();
336 Collection<BlackboardAttribute> bbattributes = Arrays.asList(
337 new BlackboardAttribute(
338 TSK_DATETIME, MODULE_NAME,
339 resultSet.getLong(
"epoch_date")),
340 new BlackboardAttribute(
341 TSK_DESCRIPTION, MODULE_NAME,
343 new BlackboardAttribute(
344 TSK_TL_EVENT_TYPE, MODULE_NAME,
345 eventType.getTypeID()));
348 BlackboardArtifact bbart = resolvedFile.newDataArtifact(
new BlackboardArtifact.Type(TSK_TL_EVENT), bbattributes);
355 blackboard.postArtifact(bbart, MODULE_NAME);
356 }
catch (BlackboardException ex) {
357 logger.log(Level.SEVERE,
"Error Posting Artifact.", ex);
359 }
catch (TskCoreException ex) {
360 logger.log(Level.SEVERE,
"Exception Adding Artifact.", ex);
366 logger.log(Level.INFO, String.format(
"PlasoDB was empty: %s", plasoDb));
369 }
catch (SQLException ex) {
370 logger.log(Level.SEVERE,
"Error while trying to read into a sqlite db.", ex);
376 Path path = Paths.get(file);
377 String fileName = path.getFileName().toString();
378 String filePath = path.getParent().toString().replaceAll(
"\\\\",
"/");
379 if (filePath.endsWith(
"/") ==
false) {
385 if (previousFile != null
386 && previousFile.getName().equalsIgnoreCase(fileName)
387 && previousFile.getParentPath().equalsIgnoreCase(filePath)) {
392 List<AbstractFile> abstractFiles = fileManager.
findFiles(fileName, filePath);
393 if (abstractFiles.size() == 1) {
394 return abstractFiles.get(0);
396 for (AbstractFile resolvedFile : abstractFiles) {
398 if (filePath.equalsIgnoreCase(resolvedFile.getParentPath())) {
400 previousFile = resolvedFile;
404 }
catch (TskCoreException ex) {
405 logger.log(Level.SEVERE,
"Exception finding file.", ex);
421 private TimelineEventType
findEventSubtype(String fileName, ResultSet row)
throws SQLException {
422 switch (row.getString(
"source")) {
424 if (fileName.toLowerCase().contains(COOKIE)
425 || row.getString(
"type").toLowerCase().contains(COOKIE)) {
427 return TimelineEventType.WEB_COOKIE;
429 return TimelineEventType.WEB_HISTORY;
433 return TimelineEventType.LOG_ENTRY;
435 switch (row.getString(
"sourcetype").toLowerCase()) {
436 case "unknown : usb entries":
437 case "unknown : usbstor entries":
438 return TimelineEventType.DEVICES_ATTACHED;
440 return TimelineEventType.REGISTRY;
443 return TimelineEventType.OTHER;
467 try (BufferedWriter writer = Files.newBufferedWriter(outputPath.resolve(
"log2timeline_output.txt"));) {
468 String line = log2TimeLineOutpout.readLine();
469 while (cancelled ==
false && nonNull(line)) {
473 line = log2TimeLineOutpout.readLine();
476 }
catch (IOException ex) {
477 logger.log(Level.WARNING,
"Error reading log2timeline output stream.", ex);
FileManager getFileManager()
void startUp(IngestJobContext context)
static int execute(ProcessBuilder processBuilder)
AbstractFile getAbstractFile(String file)
final PlasoModuleSettings settings
ResultSet executeQry(String sqlStatement)
List< AbstractFile > findFiles(String fileName)
L2TStatusProcessor(BufferedReader log2TimeLineOutpout, DataSourceIngestModuleProgress statusHelper, Path outputPath)
static ProcessBuilder buildProcessWithRunAsInvoker(String...commandLine)
final BufferedReader log2TimeLineOutpout
static int waitForTermination(String processName, Process process, long terminationCheckInterval, TimeUnit units, ProcessTerminator terminator)
static final String PLASO64
static final String PLASO
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
static final String COOKIE
static final String LOG2TIMELINE_EXECUTABLE
final DataSourceIngestModuleProgress statusHelper
static final Logger logger
static void info(String title, String message)
volatile boolean cancelled
static final String MODULE_NAME
ProcessBuilder buildPsortCommand(Path moduleOutputPath)
void postMessage(final IngestMessage message)
static final int LOG2TIMELINE_WORKERS
SleuthkitCase getSleuthkitCase()
String getModuleDirectory()
boolean dataSourceIngestIsCancelled()
static final TimeUnit TERMINATION_CHECK_INTERVAL_UNITS
static void error(String title, String message)
void switchToDeterminate(int workUnits)
static final long TERMINATION_CHECK_INTERVAL
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper)
ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image)
static File locateExecutable(String executableName)
TimelineEventType findEventSubtype(String fileName, ResultSet row)
static final String PLASO32
void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper)
void progress(int workUnits)
static final String PSORT_EXECUTABLE
File log2TimeLineExecutable
static synchronized IngestServices getInstance()
AbstractFile previousFile