19package org.sleuthkit.autopsy.modules.leappanalyzers;
21import java.io.BufferedReader;
23import java.io.FileNotFoundException;
24import java.io.FileReader;
25import java.io.IOException;
26import java.io.UncheckedIOException;
27import java.nio.file.Files;
28import java.nio.file.Path;
29import java.nio.file.Paths;
30import java.text.SimpleDateFormat;
31import java.util.Arrays;
33import java.util.ArrayList;
34import java.util.Locale;
35import java.util.logging.Level;
36import java.util.stream.Collectors;
37import java.util.stream.Stream;
38import org.apache.commons.io.FilenameUtils;
39import org.openide.modules.InstalledFileLocator;
40import org.openide.util.NbBundle;
41import org.sleuthkit.autopsy.casemodule.Case;
42import static org.sleuthkit.autopsy.casemodule.Case.getCurrentCase;
43import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
44import org.sleuthkit.autopsy.casemodule.services.FileManager;
45import org.sleuthkit.autopsy.coreutils.ExecUtil;
46import org.sleuthkit.autopsy.coreutils.Logger;
47import org.sleuthkit.autopsy.coreutils.PlatformUtil;
48import org.sleuthkit.autopsy.datamodel.ContentUtils;
49import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
50import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
51import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
52import org.sleuthkit.autopsy.ingest.IngestJobContext;
53import org.sleuthkit.autopsy.ingest.IngestMessage;
54import org.sleuthkit.autopsy.ingest.IngestServices;
55import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
56import org.sleuthkit.datamodel.AbstractFile;
57import org.sleuthkit.datamodel.Content;
58import org.sleuthkit.datamodel.FileSystem;
59import org.sleuthkit.datamodel.Image;
60import org.sleuthkit.datamodel.LocalFilesDataSource;
61import org.sleuthkit.datamodel.ReadContentInputStream;
62import org.sleuthkit.datamodel.TskCoreException;
63import org.sleuthkit.datamodel.TskData;
73 private static final String
ALEAPP =
"aLeapp";
78 private static final String
XMLFILE =
"aleapp-artifact-attribute-reference.xml";
83 new String[]{
"build.prop",
"/system/"},
84 new String[]{
"packages.xml",
"/data/system/"},
85 new String[]{
"settings.db",
"/data/com.android.providers.settings/databases/"});
93 ALeappAnalyzerIngestModule() {
98 "ALeappAnalyzerIngestModule.executable.not.found=aLeapp Executable Not Found.",
99 "ALeappAnalyzerIngestModule.requires.windows=aLeapp module requires windows.",
100 "ALeappAnalyzerIngestModule.error.aleapp.file.processor.init=Failure to initialize aLeappProcessFile"})
106 throw new IngestModuleException(NbBundle.getMessage(
this.getClass(),
"AleappAnalyzerIngestModule.not.64.bit.os"));
116 throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_error_aleapp_file_processor_init(), ex);
121 }
catch (FileNotFoundException exception) {
122 logger.log(Level.WARNING,
"aLeapp executable not found.", exception);
123 throw new IngestModuleException(Bundle.ALeappAnalyzerIngestModule_executable_not_found(), exception);
129 "ALeappAnalyzerIngestModule.error.running.aLeapp=Error running aLeapp, see log file.",
130 "ALeappAnalyzerIngestModule.error.creating.output.dir=Error creating aLeapp module output directory.",
131 "ALeappAnalyzerIngestModule.running.aLeapp=Running aLeapp",
132 "ALeappAnalyzerIngestModule_processing_aLeapp_results=Processing aLeapp results",
133 "ALeappAnalyzerIngestModule.has.run=aLeapp",
134 "ALeappAnalyzerIngestModule.aLeapp.cancelled=aLeapp run was canceled",
135 "ALeappAnalyzerIngestModule.completed=aLeapp Processing Completed",
136 "ALeappAnalyzerIngestModule.report.name=aLeapp Html Report",
137 "ALeappAnalyzerIngestModule.notAndroid.skipped=aLeapp skipped: data source does not appear to be an Android device."})
142 logger.log(Level.INFO,
"aLeapp: data source does not appear to be Android, skipping.");
144 MODULE_NAME, Bundle.ALeappAnalyzerIngestModule_notAndroid_skipped());
150 statusHelper.
progress(Bundle.ALeappAnalyzerIngestModule_running_aLeapp());
155 Files.createDirectories(tempOutputPath);
156 }
catch (IOException ex) {
157 logger.log(Level.SEVERE, String.format(
"Error creating aLeapp output directory %s", tempOutputPath.toString()), ex);
162 List<String> aLeappPathsToProcess;
167 logger.log(Level.SEVERE, String.format(
"Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
172 if (aLeappPathsToProcess.isEmpty()) {
173 logger.log(Level.SEVERE, String.format(
"Error getting file paths to search, list is empty"));
177 }
catch (IOException ex) {
178 logger.log(Level.SEVERE, String.format(
"Error when trying to execute aLeapp program getting file paths to search"), ex);
183 if ((
context.getDataSource() instanceof LocalFilesDataSource)) {
189 List<AbstractFile> aLeappFilesToProcess =
LeappFileProcessor.findLeappFilesToProcess(dataSource);
190 if (!aLeappFilesToProcess.isEmpty()) {
192 Integer filesProcessedCount = 0;
193 for (AbstractFile aLeappFile : aLeappFilesToProcess) {
194 processALeappFile(dataSource, currentCase, statusHelper, filesProcessedCount, aLeappFile);
195 filesProcessedCount++;
201 statusHelper.
progress(Bundle.ALeappAnalyzerIngestModule_processing_aLeapp_results());
203 processALeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
206 Bundle.ALeappAnalyzerIngestModule_has_run(),
207 Bundle.ALeappAnalyzerIngestModule_completed());
223 AbstractFile aLeappFile) {
224 statusHelper.
progress(NbBundle.getMessage(
this.getClass(),
"ALeappAnalyzerIngestModule.processing.file", aLeappFile.getName()), filesProcessedCount);
225 String currentTime =
new SimpleDateFormat(
"yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());
228 Files.createDirectories(moduleOutputPath);
229 }
catch (IOException ex) {
230 logger.log(Level.SEVERE, String.format(
"Error creating aLeapp output directory %s", moduleOutputPath.toString()), ex);
234 ProcessBuilder aLeappCommand =
buildaLeappCommand(moduleOutputPath, aLeappFile.getLocalAbsPath(), aLeappFile.getNameExtension());
238 logger.log(Level.WARNING, String.format(
"Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
244 }
catch (IOException ex) {
245 logger.log(Level.SEVERE, String.format(
"Error when trying to execute aLeapp program against file %s", aLeappFile.getLocalAbsPath()), ex);
249 if (
context.dataSourceIngestIsCancelled()) {
250 logger.log(Level.INFO,
"aLeapp Analyser ingest module run was canceled");
267 statusHelper.
progress(NbBundle.getMessage(
this.getClass(),
"ALeappAnalyzerIngestModule.processing.filesystem"));
268 String currentTime =
new SimpleDateFormat(
"yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());
271 Files.createDirectories(moduleOutputPath);
272 }
catch (IOException ex) {
273 logger.log(Level.SEVERE, String.format(
"Error creating aLeapp output directory %s", moduleOutputPath.toString()), ex);
277 ProcessBuilder aLeappCommand =
buildaLeappCommand(moduleOutputPath, directoryToProcess,
"fs");
281 logger.log(Level.WARNING, String.format(
"Error when trying to execute aLeapp program getting file paths to search for result is %d", result));
287 }
catch (IOException ex) {
288 logger.log(Level.SEVERE, String.format(
"Error when trying to execute aLeapp program against file system"), ex);
292 if (
context.dataSourceIngestIsCancelled()) {
293 logger.log(Level.INFO,
"aLeapp Analyser ingest module run was canceled");
309 private ProcessBuilder
buildaLeappCommand(Path moduleOutputPath, String sourceFilePath, String aLeappFileSystemType) {
313 "-t", aLeappFileSystemType,
314 "-i", sourceFilePath,
315 "-o", moduleOutputPath.toString(),
318 processBuilder.directory(moduleOutputPath.toFile());
319 processBuilder.redirectError(moduleOutputPath.resolve(
"aLeapp_err.txt").toFile());
320 processBuilder.redirectOutput(moduleOutputPath.resolve(
"aLeapp_out.txt").toFile());
321 return processBuilder;
331 processBuilder.directory(moduleOutputPath.toFile());
332 processBuilder.redirectError(moduleOutputPath.resolve(
"aLeapp_paths_error.txt").toFile());
333 processBuilder.redirectOutput(moduleOutputPath.resolve(
"aLeapp_paths.txt").toFile());
334 return processBuilder;
338 ProcessBuilder processBuilder =
new ProcessBuilder(commandLine);
343 processBuilder.environment().put(
"__COMPAT_LAYER",
"RunAsInvoker");
344 return processBuilder;
348 String executableToFindName = Paths.get(
ALEAPP, executableName).toString();
350 File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, ALeappAnalyzerIngestModule.class.getPackage().getName(),
false);
351 if (
null == exeFile || exeFile.canExecute() ==
false) {
352 throw new FileNotFoundException(executableName +
" executable not found.");
362 List<String> allIndexFiles =
new ArrayList<>();
364 try (Stream<Path> walk = Files.walk(aLeappOutputDir)) {
366 allIndexFiles = walk.map(x -> x.toString())
367 .filter(f -> f.toLowerCase().endsWith(
"index.html")).collect(Collectors.toList());
369 if (!allIndexFiles.isEmpty()) {
371 String filePath = FilenameUtils.getFullPathNoEndSeparator(allIndexFiles.get(0));
372 File dataFilesDir =
new File(Paths.get(filePath,
"_TSV Exports").toString());
373 if (dataFilesDir.exists()) {
374 currentCase.
addReport(allIndexFiles.get(0),
MODULE_NAME, Bundle.ALeappAnalyzerIngestModule_report_name());
378 }
catch (IOException | UncheckedIOException | TskCoreException ex) {
380 logger.log(Level.WARNING, String.format(
"Error finding index file in path %s", aLeappOutputDir.toString()), ex);
389 private List<String>
loadAleappPathFile(Path moduleOutputPath)
throws FileNotFoundException, IOException {
390 List<String> aLeappPathsToProcess =
new ArrayList<>();
394 try (BufferedReader reader =
new BufferedReader(
new FileReader(filePath.toString()))) {
395 String line = reader.readLine();
396 while (line !=
null) {
397 if (line.contains(
"path list generation") || line.length() < 2) {
398 line = reader.readLine();
401 aLeappPathsToProcess.add(line.trim());
402 line = reader.readLine();
406 return aLeappPathsToProcess;
410 FileManager fileManager = getCurrentCase().getServices().getFileManager();
412 for (String fullFilePath : aLeappPathsToProcess) {
414 if (
context.dataSourceIngestIsCancelled()) {
415 logger.log(Level.INFO,
"aLeapp Analyser ingest module run was canceled");
419 String ffp = fullFilePath.replaceAll(
"\\*",
"%");
420 ffp = FilenameUtils.normalize(ffp,
true);
421 String fileName = FilenameUtils.getName(ffp);
422 String filePath = FilenameUtils.getPath(ffp);
424 List<AbstractFile> aLeappFiles =
new ArrayList<>();
426 if (filePath.isEmpty()) {
427 aLeappFiles = fileManager.
findFiles(dataSource, fileName);
429 aLeappFiles = fileManager.
findFiles(dataSource, fileName, filePath);
431 }
catch (TskCoreException ex) {
432 logger.log(Level.WARNING,
"No files found to process");
436 for (AbstractFile aLeappFile : aLeappFiles) {
437 Path parentPath = Paths.get(moduleOutputPath.toString(), aLeappFile.getParentPath());
438 File fileParentPath =
new File(parentPath.toString());
445 private void extractFileToOutput(Content dataSource, AbstractFile aLeappFile, File fileParentPath, Path parentPath) {
446 if (fileParentPath.exists()) {
447 if (!aLeappFile.isDir()) {
451 Files.createDirectories(Paths.get(parentPath.toString(), aLeappFile.getName()));
452 }
catch (IOException ex) {
453 logger.log(Level.INFO, String.format(
"Error creating aLeapp output directory %s", parentPath.toString()), ex);
458 Files.createDirectories(parentPath);
459 }
catch (IOException ex) {
460 logger.log(Level.INFO, String.format(
"Error creating aLeapp output directory %s", parentPath.toString()), ex);
462 if (!aLeappFile.isDir()) {
466 Files.createDirectories(Paths.get(parentPath.toString(), aLeappFile.getName()));
467 }
catch (IOException ex) {
468 logger.log(Level.INFO, String.format(
"Error creating aLeapp output directory %s", parentPath.toString()), ex);
474 private void writeaLeappFile(Content dataSource, AbstractFile aLeappFile, String parentPath) {
475 String fileName = aLeappFile.getName().replace(
":",
"-");
476 if (!fileName.matches(
".") && !fileName.matches(
"..") && !fileName.toLowerCase().endsWith(
"-slack")) {
477 Path filePath = Paths.get(parentPath, fileName);
478 File localFile =
new File(filePath.toString());
481 }
catch (ReadContentInputStream.ReadContentInputStreamException ex) {
482 logger.log(Level.WARNING, String.format(
"Error reading file '%s' (id=%d).",
483 aLeappFile.getName(), aLeappFile.getId()), ex);
484 }
catch (IOException ex) {
485 logger.log(Level.WARNING, String.format(
"Error writing file local file '%s' (id=%d).",
486 filePath.toString(), aLeappFile.getId()), ex);
503 if (dataSource instanceof Image) {
505 boolean hasAndroidFs =
false;
506 boolean hasDefinitelyNonAndroidFs =
false;
507 for (FileSystem fs : ((Image) dataSource).getFileSystems()) {
508 switch (fs.getFsType()) {
509 case TSK_FS_TYPE_EXT3:
510 case TSK_FS_TYPE_EXT4:
511 case TSK_FS_TYPE_YAFFS2:
512 case TSK_FS_TYPE_YAFFS2_DETECT:
517 case TSK_FS_TYPE_NTFS:
518 case TSK_FS_TYPE_NTFS_DETECT:
519 case TSK_FS_TYPE_HFS:
520 case TSK_FS_TYPE_HFS_DETECT:
521 case TSK_FS_TYPE_APFS:
522 case TSK_FS_TYPE_APFS_DETECT:
523 hasDefinitelyNonAndroidFs =
true;
532 if (hasDefinitelyNonAndroidFs) {
536 }
catch (TskCoreException ex) {
537 logger.log(Level.WARNING,
"Error checking filesystem types for aLeapp Android detection", ex);
552 FileManager fileManager = getCurrentCase().getServices().getFileManager();
555 if (!fileManager.
findFiles(dataSource, fileInfo[0], fileInfo[1]).isEmpty()) {
558 }
catch (TskCoreException ex) {
559 logger.log(Level.WARNING, String.format(
"Error searching for Android indicator file '%s'", fileInfo[0]), ex);
573 Bundle.ALeappAnalyzerIngestModule_error_running_aLeapp());
void addReport(String localPath, String srcModuleName, String reportName)
static Case getCurrentCase()
String getModuleDirectory()
String getTempDirectory()
List< AbstractFile > findFiles(String fileName)
static int execute(ProcessBuilder processBuilder)
synchronized static Logger getLogger(String name)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
void progress(int workUnits)
void switchToIndeterminate()
void switchToDeterminate(int workUnits)
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
void postMessage(final IngestMessage message)
static synchronized IngestServices getInstance()
void startUp(IngestJobContext context)
static final String ALEAPP_EXECUTABLE
ProcessBuilder buildaLeappListCommand(Path moduleOutputPath)
void writeErrorMsgToIngestInbox()
static final Logger logger
void processALeappFile(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, int filesProcessedCount, AbstractFile aLeappFile)
static ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine)
void extractFilesFromDataSource(Content dataSource, List< String > aLeappPathsToProcess, Path moduleOutputPath)
List< String > loadAleappPathFile(Path moduleOutputPath)
void writeaLeappFile(Content dataSource, AbstractFile aLeappFile, String parentPath)
void processALeappFs(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, String directoryToProcess)
boolean isAndroidDataSource(Content dataSource)
void extractFileToOutput(Content dataSource, AbstractFile aLeappFile, File fileParentPath, Path parentPath)
void addALeappReportToReports(Path aLeappOutputDir, Case currentCase)
ProcessBuilder buildaLeappCommand(Path moduleOutputPath, String sourceFilePath, String aLeappFileSystemType)
static final String ALEAPP_FS
static final List< String[]> ANDROID_INDICATOR_FILES
LeappFileProcessor aLeappFileProcessor
static final String XMLFILE
static final String MODULE_NAME
static final String ALEAPP
static final String ALEAPP_PATHS_FILE
static File locateExecutable(String executableName)
ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper)
boolean hasAndroidIndicatorFiles(Content dataSource)