diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index f27b253e16..6ff7b8fdcf 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -2,9 +2,14 @@ cannotBuildXmlParser=Unable to build XML parser: cannotLoadSEUQA=Unable to load Search Engine URL Query Analyzer settings file, SEUQAMappings.xml: cannotParseXml=Unable to parse XML file: 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_FlashDrive=Flash Drive +# {0} - OS name DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.parentModuleName=Recent Activity Extract.indexError.message=Failed to index artifact for keyword search. @@ -182,6 +187,7 @@ RecentDocumentsByLnk.parentModuleName.noSpace=RecentActivity RecentDocumentsByLnk.parentModuleName=Recent Activity 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 @@ -189,4 +195,7 @@ SearchEngineURLQueryAnalyzer.domainSubStr.none=NONE SearchEngineURLQueryAnalyzer.toString=Name: {0}\nDomain Substring: {1}\nCount: {2}\nSplit Tokens: \n{3} SearchEngineURLQueryAnalyzer.parentModuleName.noSpace=RecentActivity SearchEngineURLQueryAnalyzer.parentModuleName=Recent Activity +Shellbag_Artifact_Display_Name=Shell Bags +Shellbag_Key_Attribute_Display_Name=Key +Shellbag_Last_Write_Attribute_Display_Name=Last Write UsbDeviceIdMapper.parseAndLookup.text=Product: {0} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 6e4d0fc4c7..628b84ea08 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -68,13 +68,19 @@ import org.openide.util.Lookup; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; +import org.sleuthkit.autopsy.recentactivity.ShellBagParser.ShellBag; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; /** * Extract windows registry data using regripper. Runs two versions of @@ -85,7 +91,10 @@ import org.sleuthkit.datamodel.TskCoreException; @NbBundle.Messages({ "RegRipperNotFound=Autopsy RegRipper executable not found.", "RegRipperFullNotFound=Full version RegRipper executable not found.", - "Progress_Message_Analyze_Registry=Analyzing Registry Files" + "Progress_Message_Analyze_Registry=Analyzing Registry Files", + "Shellbag_Artifact_Display_Name=Shell Bags", + "Shellbag_Key_Attribute_Display_Name=Key", + "Shellbag_Last_Write_Attribute_Display_Name=Last Write" }) class ExtractRegistry extends Extract { @@ -132,6 +141,14 @@ class ExtractRegistry extends Extract { private final Path rrFullHome; // Path to the full version of RegRipper private Content dataSource; private IngestJobContext context; + + private static final String SHELLBAG_ARTIFACT_NAME = "RA_SHELL_BAG"; //NON-NLS + private static final String SHELLBAG_ATTRIBUTE_LAST_WRITE = "RA_SHELL_BAG_LAST_WRITE"; //NON-NLS + private static final String SHELLBAG_ATTRIBUTE_KEY= "RA_SHELL_BAG_KEY"; //NON-NLS + + BlackboardArtifact.Type shellBagArtifactType = null; + BlackboardAttribute.Type shellBagKeyAttributeType = null; + BlackboardAttribute.Type shellBagLastWriteAttributeType = null; ExtractRegistry() throws IngestModuleException { moduleName = NbBundle.getMessage(ExtractIE.class, "ExtractRegistry.moduleName.text"); @@ -195,6 +212,13 @@ class ExtractRegistry extends Extract { } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error fetching 'ntuser.dat' file."); //NON-NLS } + + // find the user-specific ntuser-dat files + try { + allRegistryFiles.addAll(fileManager.findFiles(dataSource, "usrclass.dat")); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Error finding 'usrclass.dat' files."), ex); //NON-NLS + } // find the system hives' String[] regFileNames = new String[]{"system", "software", "security", "sam"}; //NON-NLS @@ -204,7 +228,7 @@ class ExtractRegistry extends Extract { } catch (TskCoreException ex) { String msg = NbBundle.getMessage(this.getClass(), "ExtractRegistry.findRegFiles.errMsg.errReadingFile", regFileName); - logger.log(Level.WARNING, msg); + logger.log(Level.WARNING, msg, ex); this.addErrorMessage(this.getName() + ": " + msg); } } @@ -282,6 +306,13 @@ class ExtractRegistry extends Extract { this.addErrorMessage( NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults", this.getName(), regFileName)); + } else if (regFileNameLocal.toLowerCase().contains("ntuser") || regFileNameLocal.toLowerCase().contains("usrclass")) { + try { + List shellbags = ShellBagParser.parseShellbagOutput(regOutputFiles.fullPlugins); + createShellBagArtifacts(regFile, shellbags); + } catch (IOException | TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to get shell bags from file %s", regOutputFiles.fullPlugins), ex); + } } try { Report report = currentCase.addReport(regOutputFiles.fullPlugins, @@ -340,6 +371,8 @@ class ExtractRegistry extends Extract { fullType = "sam"; //NON-NLS } else if (regFilePath.toLowerCase().contains("security")) { //NON-NLS fullType = "security"; //NON-NLS + }else if (regFilePath.toLowerCase().contains("usrclass")) { //NON-NLS + fullType = "usrclass"; //NON-NLS } else { return regOutputFiles; } @@ -840,13 +873,13 @@ class ExtractRegistry extends Extract { } // for return true; } catch (FileNotFoundException ex) { - logger.log(Level.SEVERE, "Error finding the registry file.", ex); //NON-NLS + logger.log(Level.WARNING, String.format("Error finding the registry file: %s", regFilePath), ex); //NON-NLS } catch (SAXException ex) { - logger.log(Level.SEVERE, "Error parsing the registry XML.", ex); //NON-NLS + logger.log(Level.WARNING, String.format("Error parsing the registry XML: %s", regFilePath), ex); //NON-NLS } catch (IOException ex) { - logger.log(Level.SEVERE, "Error building the document parser.", ex); //NON-NLS + logger.log(Level.WARNING, String.format("Error building the document parser: %s", regFilePath), ex); //NON-NLS } catch (ParserConfigurationException ex) { - logger.log(Level.SEVERE, "Error configuring the registry parser.", ex); //NON-NLS + logger.log(Level.WARNING, String.format("Error configuring the registry parser: %s", regFilePath), ex); //NON-NLS } finally { try { if (fstream != null) { @@ -1119,7 +1152,120 @@ class ExtractRegistry extends Extract { } } + /** + * Create the shellbag artifacts from the list of ShellBag objects. + * + * @param regFile The data source file + * @param shellbags List of shellbags from source file + * + * @throws TskCoreException + */ + void createShellBagArtifacts(AbstractFile regFile, List shellbags) throws TskCoreException { + List artifacts = new ArrayList<>(); + for (ShellBag bag : shellbags) { + Collection attributes = new ArrayList<>(); + BlackboardArtifact artifact = regFile.newArtifact(getShellBagArtifact().getTypeID()); + attributes.add(new BlackboardAttribute(TSK_PATH, getName(), bag.getResource())); + attributes.add(new BlackboardAttribute(getKeyAttribute(), getName(), bag.getKey())); + + long time; + time = bag.getLastWrite(); + if (time != 0) { + attributes.add(new BlackboardAttribute(getLastWriteAttribute(), getName(), time)); + } + + time = bag.getModified(); + if (time != 0) { + attributes.add(new BlackboardAttribute(TSK_DATETIME_MODIFIED, getName(), time)); + } + + time = bag.getCreated(); + if (time != 0) { + attributes.add(new BlackboardAttribute(TSK_DATETIME_CREATED, getName(), time)); + } + + time = bag.getAccessed(); + if (time != 0) { + attributes.add(new BlackboardAttribute(TSK_DATETIME_ACCESSED, getName(), time)); + } + + artifact.addAttributes(attributes); + + artifacts.add(artifact); + } + + postArtifacts(artifacts); + } + + /** + * Returns the custom Shellbag artifact type or creates it if it does not + * currently exist. + * + * @return BlackboardArtifact.Type for shellbag artifacts + * + * @throws TskCoreException + */ + private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException { + if (shellBagArtifactType == null) { + try { + tskCase.addBlackboardArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name()); //NON-NLS + } catch (TskDataException ex) { + // Artifact already exists + logger.log(Level.INFO, String.format("%s may have already been defined for this case", SHELLBAG_ARTIFACT_NAME), ex); + } + + shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME); + } + + return shellBagArtifactType; + } + + /** + * Gets the custom BlackboardAttribute type. The attribute type is created + * if it does not currently exist. + * + * @return The BlackboardAttribute type + * + * @throws TskCoreException + */ + private BlackboardAttribute.Type getLastWriteAttribute() throws TskCoreException { + if (shellBagLastWriteAttributeType == null) { + try { + shellBagLastWriteAttributeType = tskCase.addArtifactAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE, + BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME, + Bundle.Shellbag_Last_Write_Attribute_Display_Name()); + } catch (TskDataException ex) { + // Attribute already exists get it from the case + shellBagLastWriteAttributeType = tskCase.getAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE); + } + } + return shellBagLastWriteAttributeType; + } + + /** + * Gets the custom BlackboardAttribute type. The attribute type is created + * if it does not currently exist. + * + * @return The BlackboardAttribute type + * + * @throws TskCoreException + */ + private BlackboardAttribute.Type getKeyAttribute() throws TskCoreException { + if (shellBagKeyAttributeType == null) { + try { + shellBagKeyAttributeType = tskCase.addArtifactAttributeType(SHELLBAG_ATTRIBUTE_KEY, + BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, + Bundle.Shellbag_Key_Attribute_Display_Name()); + } catch (TskDataException ex) { + // The attribute already exists get it from the case + shellBagKeyAttributeType = tskCase.getAttributeType(SHELLBAG_ATTRIBUTE_KEY); + } + } + return shellBagKeyAttributeType; + } + + /** * Maps the user groups to the sid that are a part of them. * * @param bufferedReader diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ShellBagParser.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ShellBagParser.java new file mode 100755 index 0000000000..a7de5ebf5c --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ShellBagParser.java @@ -0,0 +1,362 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * + * Copyright 2012 42six Solutions. + * Contact: aebadirad 42six com + * Project Contact/Architect: 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.recentactivity; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Parse the ntuser and ursclass regripper output files for shellbags. + */ +public class ShellBagParser { + private static final Logger logger = Logger.getLogger(ShellBagParser.class.getName()); + + static SimpleDateFormat DATE_TIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + // Last Write date\time format from itempos plugin + static SimpleDateFormat DATE_TIME_FORMATTER2 = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyyy", Locale.getDefault()); + + private ShellBagParser() { + } + + /** + * Parse the given file for shell bags. + * + * @param regFilePath Regripper output file + * + * @return List of the found shellbags + * + * @throws FileNotFoundException + * @throws IOException + */ + static List parseShellbagOutput(String regFilePath) throws FileNotFoundException, IOException { + List shellbags = new ArrayList<>(); + File regfile = new File(regFilePath); + + ShellBagParser sbparser = new ShellBagParser(); + + try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) { + String line = reader.readLine(); + while (line != null) { + line = line.trim(); + + if (line.matches("^shellbags_xp v.*")) { + shellbags.addAll(sbparser.parseShellBagsXP(reader)); + } else if (line.matches("^shellbags v.*")) { + shellbags.addAll(sbparser.parseShellBags(reader)); + } else if (line.matches("^itempos.*")) { + shellbags.addAll(sbparser.parseItempos(reader)); + } + + line = reader.readLine(); + } + } + + return shellbags; + } + + /** + * Parse the output from the shellbag_xp plugin. + * + * @param reader File reader + * + * @return List of found shellbags + * + * @throws IOException + */ + List parseShellBagsXP(BufferedReader reader) throws IOException { + List shellbags = new ArrayList<>(); + String line = reader.readLine(); + + while (line != null && !isSectionSeparator(line)) { + + if (isShellbagXPDataLine(line)) { + String[] tokens = line.split("\\|"); + if (tokens.length >= 6) { + shellbags.add(new ShellBag(tokens[5].trim(), "Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU", tokens[0].trim(), tokens[1].trim(), tokens[2].trim(), tokens[3].trim())); + } + } + + line = reader.readLine(); + } + + return shellbags; + } + + /** + * Parse the output of the shellbags regripper plugin. + * + * @param reader + * @return List of found shellbags + * + * @throws IOException + */ + List parseShellBags(BufferedReader reader) throws IOException { + List shellbags = new ArrayList<>(); + String line = reader.readLine(); + String regPath = "Local Settings\\Software\\Microsoft\\Windows\\Shell\\BagMRU"; + + while (line != null && !isSectionSeparator(line)) { + + if (isShellbagDataLine(line)) { + String[] tokens = line.split("\\|"); + String path = tokens[6].replaceAll("\\[.*?\\]", "").trim(); + int index = line.lastIndexOf('['); + String endstuff = ""; + if (index != -1) { + endstuff = line.substring(index, line.length() - 1).replace("[Desktop", ""); + } + if (tokens.length >= 7) { + shellbags.add(new ShellBag(path, regPath + endstuff, tokens[0].trim(), tokens[1].trim(), tokens[2].trim(), tokens[3].trim())); + } + } + + line = reader.readLine(); + } + + return shellbags; + } + + /** + * Parse the output of the Itempos regripper plugin. + * + * @param reader + * + * @return List of found shell bags. + * + * @throws IOException + */ + List parseItempos(BufferedReader reader) throws IOException { + List shellbags = new ArrayList<>(); + String bagpath = ""; + String lastWrite = ""; + String line = reader.readLine(); + + while (line != null && !isSectionSeparator(line)) { + + if (isItemposDataLine(line)) { + String[] tokens = line.split("\\|"); + if (tokens.length >= 5) { + shellbags.add(new ShellBag(tokens[4].trim(), bagpath, lastWrite, tokens[1].trim(), tokens[2].trim(), tokens[3].trim())); + } + } else if (line.contains("Software\\")) { + bagpath = line.trim(); + lastWrite = ""; + } else if (line.contains("LastWrite:")) { + lastWrite = line.replace("LastWrite:", "").trim(); + } + + line = reader.readLine(); + } + + return shellbags; + } + + /** + * Return whether or not the given line is a plugin output separator. + * + * The format of the plugin output separators is: + * ---------------------------------------- + * + * @param line + * + * @return True if the line is a section separator + */ + boolean isSectionSeparator(String line) { + if (line == null || line.isEmpty()) { + return false; + } + + return line.trim().matches("^-+"); + } + + /** + * This data rows from the itempos plugin are in the format: + * | | | | + * The times are in the format YYYY-MM-dd HH:mm:ss + * + * @param line + * + * @return + */ + boolean isItemposDataLine(String line) { + return line.matches("^\\d*?\\s*?\\|.*?\\|.*?\\|.*?\\|.*?"); + } + + /** + * The data rows from the shellbags_xp plug look like + * | | | | + * | + * + * The times are in the format YYYY-MM-dd HH:mm:ss + * + * @param line + * + * @return + */ + boolean isShellbagXPDataLine(String line) { + return line.matches("^(\\d+?.*?\\s*? | \\s*?)\\|.*?\\|.*?\\|.*?\\|.*?\\|.*?"); + } + + /** + * The data rows from the shellbags plug look like + * | | | | + * | + * + * The times are in the format YYYY-MM-dd HH:mm:ss + * + * @param line + * + * @return + */ + boolean isShellbagDataLine(String line) { + return line.matches("^(\\d+?.*?\\s*? | \\s*?)\\|.*?\\|.*?\\|.*?\\|.*?\\|.*?\\|.*?"); + } + + /** + * Class to hold the shell bag data. + * + */ + class ShellBag { + + private final String resource; + private final String key; + private final String lastWrite; + private final String modified; + private final String accessed; + private final String created; + + /** + * Creates a new shell bag object. + * + * Any of the parameters can be ""; + * + * @param resource String from the "Resource" or "Name" column, depending on the plug in + * @param key String registry key value + * @param lastWrite Depending on the plug in lastWrite is either Last write value or the MRU Time value + * @param modified Modified time string + * @param accessed Accessed time string + * @param created Created time string + */ + ShellBag(String resource, String key, String lastWrite, String modified, String accessed, String created) { + this.resource = resource; + this.key = key; + this.lastWrite = lastWrite; + this.accessed = accessed; + this.modified = modified; + this.created = created; + } + + /** + * Returns the resource string. + * + * @return The shellbag resource or empty string. + */ + String getResource() { + return resource == null ? "" : resource; + } + + /** + * Returns the key string. + * + * @return The shellbag key or empty string. + */ + String getKey() { + return key == null ? "" : key; + } + + /** + * Returns the last time in seconds since java epoch or + * 0 if no valid time was found. + * + * @return The time in seconds or 0 if no valid time. + */ + long getLastWrite() { + return parseDateTime(lastWrite); + } + + /** + * Returns the last time in seconds since java epoch or + * 0 if no valid time was found. + * + * @return The time in seconds or 0 if no valid time. + */ + long getModified() { + return parseDateTime(modified); + } + + /** + * Returns the last time in seconds since java epoch or + * 0 if no valid time was found. + * + * @return The time in seconds or 0 if no valid time. + */ + long getAccessed() { + return parseDateTime(accessed); + } + + /** + * Returns the last time in seconds since java epoch or + * 0 if no valid time was found. + * + * @return The time in seconds or 0 if no valid time. + */ + long getCreated() { + return parseDateTime(created); + } + + /** + * Returns the date\time in seconds from epoch for the given string with + * format yyyy-MM-dd HH:mm:ss; + * + * @param dateTimeString String of format yyyy-MM-dd HH:mm:ss + * + * @return time in seconds from java epoch + */ + long parseDateTime(String dateTimeString) { + if (!dateTimeString.isEmpty()) { + try { + return DATE_TIME_FORMATTER.parse(dateTimeString).getTime() / 1000; + } catch (ParseException ex) { + // The parse of the string may fail because there are two possible formats. + } + + try { + return DATE_TIME_FORMATTER2.parse(dateTimeString).getTime() / 1000; + } catch (ParseException ex) { + logger.log(Level.WARNING, String.format("ShellBag parse failure. %s is not formated as expected.", dateTimeString), ex); + } + } + return 0; + } + } + +}