diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index b796a16d26..5b2b92e49b 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -5,15 +5,10 @@ ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for an ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis. ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s. ChromeCacheExtractor.moduleName=ChromeCacheExtractor -# {0} - module name -# {1} - row number -# {2} - table length -# {3} - cache path ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3} DataSourceUsage_AndroidMedia=Android Media Card DataSourceUsage_DJU_Drone_DAT=DJI Internal SD Card DataSourceUsage_FlashDrive=Flash Drive -# {0} - OS name DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.parentModuleName=Recent Activity DefaultPriorityDomainCategorizer_searchEngineCategory=Search Engine @@ -28,7 +23,6 @@ ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Ed ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file -# {0} - sub module name ExtractIE_executePasco_errMsg_errorRunningPasco={0}: Error analyzing Internet Explorer web history ExtractOs.androidOs.label=Android ExtractOs.androidVolume.label=OS Drive (Android) @@ -61,7 +55,6 @@ ExtractOs.windowsVolume.label=OS Drive (Windows) ExtractOs.yellowDogLinuxOs.label=Linux (Yellow Dog) ExtractOs.yellowDogLinuxVolume.label=OS Drive (Linux Yellow Dog) ExtractOS_progressMessage=Checking for OS -# {0} - sub module name ExtractPrefetch_errMsg_prefetchParsingFailed={0}: Error analyzing prefetch files ExtractPrefetch_module_name=Windows Prefetch Extractor ExtractRecycleBin_module_name=Recycle Bin @@ -88,6 +81,8 @@ ExtractZone_process_errMsg_find=A failure occured while searching for :Zone.Inde ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone +Jumplist_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis. +Jumplist_module_name=Windows Jumplist Extractor OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity @@ -157,19 +152,13 @@ Firefox.getDlV24.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{ Firefox.getDlV24.errMsg.errParsingArtifacts={0}: Error parsing {1} Firefox web download artifacts. Progress_Message_Analyze_Registry=Analyzing Registry Files Progress_Message_Analyze_Usage=Data Sources Usage Analysis -# {0} - browserName Progress_Message_Chrome_AutoFill=Chrome Auto Fill Browser {0} -# {0} - browserName Progress_Message_Chrome_Bookmarks=Chrome Bookmarks Browser {0} Progress_Message_Chrome_Cache=Chrome Cache -# {0} - browserName Progress_Message_Chrome_Cookies=Chrome Cookies Browser {0} -# {0} - browserName Progress_Message_Chrome_Downloads=Chrome Downloads Browser {0} Progress_Message_Chrome_FormHistory=Chrome Form History -# {0} - browserName Progress_Message_Chrome_History=Chrome History Browser {0} -# {0} - browserName Progress_Message_Chrome_Logins=Chrome Logins Browser {0} Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks Progress_Message_Edge_Cookies=Microsoft Edge Cookies @@ -224,7 +213,6 @@ Recently_Used_Artifacts_Winrar=Recently opened according to WinRAR MRU Registry_System_Bam=Recently Executed according to Background Activity Moderator (BAM) RegRipperFullNotFound=Full version RegRipper executable not found. RegRipperNotFound=Autopsy RegRipper executable not found. -# {0} - file name SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.engineName.none=NONE diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractJumpLists.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractJumpLists.java new file mode 100644 index 0000000000..23f5d08a3d --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractJumpLists.java @@ -0,0 +1,233 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * + * + * 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.recentactivity; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.apache.poi.poifs.filesystem.DirectoryEntry; +import org.apache.poi.poifs.filesystem.DocumentEntry; +import org.apache.poi.poifs.filesystem.DocumentInputStream; +import org.apache.poi.poifs.filesystem.Entry; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.coreutils.JLNK; +import org.sleuthkit.autopsy.coreutils.JLnkParser; +import org.sleuthkit.autopsy.coreutils.JLnkParserException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Extract the LNK files from the jumplists and save them to ModuleOutput\RecentActivity\Jumplists + * and then add them back into the case as a dervived file. + */ +final class ExtractJumpLists extends Extract { + + private static final Logger logger = Logger.getLogger(ExtractJumpLists.class.getName()); + + private IngestJobContext context; + + private static final String JUMPLIST_TSK_COMMENT = "Jumplist File"; + private static final String RA_DIR_NAME = "RecentActivity"; //NON-NLS + private static final String MODULE_OUTPUT_DIR = "ModuleOutput"; //NON-NLS + private static final String AUTOMATIC_DESTINATIONS_FILE_DIRECTORY = "%/AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/"; + private static final String JUMPLIST_DIR_NAME = "jumplists"; //NON-NLS + private static final String VERSION_NUMBER = "1.0.0"; //NON-NLS + private String moduleName; + private FileManager fileManager; + private final IngestServices services = IngestServices.getInstance(); + + @Messages({ + "Jumplist_module_name=Windows Jumplist Extractor", + "Jumplist_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis." + }) + ExtractJumpLists() { + super(Bundle.Jumplist_module_name()); + } + + @Override + void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { + + this.context = context; + moduleName = Bundle.Jumplist_module_name(); + fileManager = currentCase.getServices().getFileManager(); + long ingestJobId = context.getJobId(); + + List jumpListFiles = extractJumplistFiles(dataSource, ingestJobId); + + if (context.dataSourceIngestIsCancelled()) { + return; + } + + List derivedFiles = new ArrayList<>(); + String derivedPath = null; + String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + JUMPLIST_DIR_NAME, ingestJobId); + for (AbstractFile jumplistFile : jumpListFiles) { + if (!jumplistFile.getName().toLowerCase().contains("-slack") && !jumplistFile.getName().equals("..") && + !jumplistFile.getName().equals(".") && jumplistFile.getSize() > 0) { + String jlFile = Paths.get(baseRaTempPath, jumplistFile.getName()).toString(); + String moduleOutPath = Case.getCurrentCase().getModuleDirectory() + File.separator + RA_DIR_NAME + File.separator + JUMPLIST_DIR_NAME + File.separator + jumplistFile.getName(); + derivedPath = RA_DIR_NAME + File.separator + JUMPLIST_DIR_NAME + File.separator + jumplistFile.getName(); + File jlDir = new File(moduleOutPath); + if (jlDir.exists() == false) { + boolean dirMade = jlDir.mkdirs(); + if (!dirMade) { + logger.log(Level.WARNING, "Error creating directory to store Jumplist LNK files %s", moduleOutPath); //NON-NLS + continue; + } + } + derivedFiles.addAll(extractLnkFiles(jlFile, moduleOutPath, jumplistFile, derivedPath)); + } + } + + // notify listeners of new files and schedule for analysis + progressBar.progress(String.format(Bundle.Jumplist_adding_extracted_files_msg(), derivedFiles.size())); + derivedFiles.forEach((derived) -> { services.fireModuleContentEvent(new ModuleContentEvent(derived)); }); + context.addFilesToJob(derivedFiles); + + } + + /** + * Find jumplist and extract jumplist files to temp directory + * + * @return - list of jumplist abstractfiles or empty list + */ + private List extractJumplistFiles(Content dataSource, Long ingestJobId) { + List jumpListFiles = new ArrayList<>();; + List tempJumpListFiles = new ArrayList<>();; + + FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); + + try { + tempJumpListFiles = fileManager.findFiles(dataSource, "%", AUTOMATIC_DESTINATIONS_FILE_DIRECTORY); //NON-NLS + if (!tempJumpListFiles.isEmpty()) { + jumpListFiles.addAll(tempJumpListFiles); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to find jumplist files.", ex); //NON-NLS + return jumpListFiles; // No need to continue + } + + for (AbstractFile jumpListFile : jumpListFiles) { + + if (context.dataSourceIngestIsCancelled()) { + return jumpListFiles; + } + + if (!jumpListFile.getName().toLowerCase().contains("-slack") && !jumpListFile.getName().equals("..") && + !jumpListFile.getName().equals(".") && jumpListFile.getSize() > 0) { + String fileName = jumpListFile.getName(); + String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + JUMPLIST_DIR_NAME, ingestJobId); + String jlFile = Paths.get(baseRaTempPath, fileName).toString(); + try { + ContentUtils.writeToFile(jumpListFile, new File(jlFile)); + } catch (IOException ex) { + logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", fileName, jlFile), ex); //NON-NLS + } + } + } + + return jumpListFiles; + + } + + /* + * Read each jumplist file and extract the lnk files to moduleoutput + */ + private List extractLnkFiles(String jumpListFile, String moduleOutPath, AbstractFile jumpListAbsFile, String derivedPath) { + + List derivedFiles = new ArrayList<>(); + DerivedFile derivedFile; + String lnkFileName = ""; + + try (POIFSFileSystem fs = new POIFSFileSystem(new File(jumpListFile))) { + DirectoryEntry root = fs.getRoot(); + for (Entry entry : root) { + if (entry instanceof DirectoryEntry) { + //If this data structure needed to recurse this is where it would do it but jjumplists do not need to at this time + continue; + } else if (entry instanceof DocumentEntry) { + String jmpListFileName = entry.getName(); + int fileSize = ((DocumentEntry) entry).getSize(); + + if (fileSize > 0) { + try (DocumentInputStream stream = fs.createDocumentInputStream(jmpListFileName)) { + byte[] buffer = new byte[stream.available()]; + stream.read(buffer); + + JLnkParser lnkParser = new JLnkParser(fs.createDocumentInputStream(jmpListFileName), fileSize); + JLNK lnk = lnkParser.parse(); + lnkFileName = lnk.getBestName() + ".lnk"; + File targetFile = new File(moduleOutPath + File.separator + entry.getName() + "-" + lnkFileName); + String derivedFileName = MODULE_OUTPUT_DIR + File.separator + derivedPath + File.separator + entry.getName() + "-" + lnkFileName; + OutputStream outStream = new FileOutputStream(targetFile); + outStream.write(buffer); + outStream.close(); + derivedFile = fileManager.addDerivedFile(lnkFileName, derivedFileName, + fileSize, + 0, + 0, + 0, + 0, // TBD + true, + jumpListAbsFile, + "", + moduleName, + VERSION_NUMBER, + "", + TskData.EncodingType.NONE); + derivedFiles.add(derivedFile); + + } catch (IOException | JLnkParserException e) { + logger.log(Level.WARNING, String.format("No such document, or the Entry represented by documentName is not a DocumentEntry link file is %s", lnkFileName), e); //NON-NLS + } + } + } else { + // currently, either an Entry is a DirectoryEntry or a DocumentEntry, + // but in the future, there may be other entry subinterfaces. + // The internal data structure certainly allows for a lot more entry types. + continue; + } + } + } catch (IOException | TskCoreException ex) { + logger.log(Level.WARNING, String.format("Error lnk parsing the file to get recent files $s", jumpListFile), ex); //NON-NLS + } + + return derivedFiles; + + } + +} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java index 290f406a8a..bf64688408 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java @@ -84,8 +84,10 @@ public final class RAImageIngestModule implements DataSourceIngestModule { Extract prefetch = new ExtractPrefetch(); Extract webAccountType = new ExtractWebAccountType(); Extract messageDomainType = new DomainCategoryRunner(); + Extract jumpList = new ExtractJumpLists(); extractors.add(recycleBin); + extractors.add(jumpList); extractors.add(recentDocuments); extractors.add(registry); // needs to run before the DataSourceUsageAnalyzer extractors.add(osExtract); // this needs to run before the DataSourceUsageAnalyzer