Merge pull request #5160 from kellykelly3/1330-shell-bags

1330 - parse shell bags
This commit is contained in:
Richard Cordovano 2019-09-17 12:43:29 -04:00 committed by GitHub
commit f654390a93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 523 additions and 6 deletions

View File

@ -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}

View File

@ -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 {
@ -133,6 +142,14 @@ class ExtractRegistry extends Extract {
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");
@ -196,6 +213,13 @@ class ExtractRegistry extends Extract {
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
for (String regFileName : regFileNames) {
@ -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<ShellBag> 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,6 +1152,119 @@ 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<ShellBag> shellbags) throws TskCoreException {
List<BlackboardArtifact> artifacts = new ArrayList<>();
for (ShellBag bag : shellbags) {
Collection<BlackboardAttribute> 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.
*

View File

@ -0,0 +1,362 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
*
* Copyright 2012 42six Solutions.
* Contact: aebadirad <at> 42six <dot> com
* Project Contact/Architect: carrier <at> sleuthkit <dot> 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<ShellBag> parseShellbagOutput(String regFilePath) throws FileNotFoundException, IOException {
List<ShellBag> 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<ShellBag> parseShellBagsXP(BufferedReader reader) throws IOException {
List<ShellBag> 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<ShellBag> parseShellBags(BufferedReader reader) throws IOException {
List<ShellBag> 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<ShellBag> parseItempos(BufferedReader reader) throws IOException {
List<ShellBag> 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:
* <size> | <Modified time> | <Accessed time> | <Created time> | <Name>
* 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
* <MRU Time> | <Modified time> | <Accessed time> | <Created time> |
* <Zip SubFolder> | <Resource>
*
* 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
* <MRU Time> | <Modified time> | <Accessed time> | <Created time> |
* <Zip SubFolder> |<MFT File Ref> <Resource>
*
* 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;
}
}
}