/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Contact: 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.File;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.mitre.cybox.objects.WindowsRegistryKey;
import org.mitre.cybox.common_2.ConditionTypeEnum;
import com.williballenthin.rejistry.*;
/**
*
*/
class EvalRegistryObj extends EvaluatableObject {
private final WindowsRegistryKey obj;
private final List<RegistryFileInfo> regFiles = new ArrayList<RegistryFileInfo>();
public EvalRegistryObj(WindowsRegistryKey a_obj, String a_id, String a_spacing, List<RegistryFileInfo> a_regFiles) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
regFiles.addAll(a_regFiles);
}
private EvalRegistryObj() {
obj = null;
id = null;
spacing = "";
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
// Key name is required
if (obj.getKey() == null) {
return new ObservableResult(id, "RegistryObject: No key found", //NON-NLS
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// For now, only support a full string match
if (!((obj.getKey().getCondition() == null)
|| (obj.getKey().getCondition() == ConditionTypeEnum.EQUALS))) {
return new ObservableResult(id, "RegistryObject: Can not support condition " + obj.getKey().getCondition() //NON-NLS
+ " on Key field", //NON-NLS
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
setUnsupportedFieldWarnings();
// Make a list of hives to test
List<RegistryFileInfo> hiveList = new ArrayList<RegistryFileInfo>();
if (obj.getHive() == null) {
// If the hive field is missing, add everything
hiveList.addAll(regFiles);
} else if (obj.getHive().getValue().toString().startsWith("HKEY")) { //NON-NLS
// If the hive name is HKEY_LOCAL_MACHINE, add the ones from the config directory.
// Otherwise, add the others
for (RegistryFileInfo regFile : regFiles) {
if (regFile.abstractFile.getParentPath() != null) {
Pattern pattern = Pattern.compile("system32", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(regFile.abstractFile.getParentPath());
if (matcher.find()) {
// Looking for system files and found one, so add it to the list
if (obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) { //NON-NLS
hiveList.add(regFile);
}
} else {
// Looking for non-system files and found one, so add it to the list
if (!obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) { //NON-NLS
hiveList.add(regFile);
}
}
}
}
} else {
// Otherwise, try to match the name
String stixHiveName = obj.getHive().getValue().toString();
// The temp files will end \Temp\STIX\(hive)_(number)
Pattern pattern = Pattern.compile("Temp.STIX." + stixHiveName, Pattern.CASE_INSENSITIVE);
for (RegistryFileInfo hive : regFiles) {
Matcher matcher = pattern.matcher(hive.tempFileName);
if (matcher.find()) {
hiveList.add(hive);
}
}
// If nothing matched, add all the files
if (hiveList.isEmpty()) {
hiveList.addAll(regFiles);
}
}
// This is unlikely to happen unless we have no registry files to test against
if (hiveList.isEmpty()) {
return new ObservableResult(id, "RegistryObject: No matching registry hives found", //NON-NLS
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
for (RegistryFileInfo hive : hiveList) {
try {
ObservableResult result = testRegistryFile(hive);
if (result.isTrue()) {
return result;
}
} catch (Exception ex) {
// The registry parser seems to throw lots of different types of exceptions,
// so make sure to catch them all by this point. Malformed registry files
// in particular cause problems.
addWarning("Error processing registry file " + hive); //NON-NLS
}
}
if (obj.getHive() == null) {
return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue(), //NON-NLS
spacing, ObservableResult.ObservableState.FALSE, null);
}
return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue() //NON-NLS
+ " in hive " + obj.getHive().getValue(), //NON-NLS
spacing, ObservableResult.ObservableState.FALSE, null);
}
/**
* Test the Registry object against one registry file.
*
* @param a_regInfo The registry file
*
* @return Result of the test
*/
private ObservableResult testRegistryFile(RegistryFileInfo a_regInfo) {
try {
RegistryKey root = openRegistry(a_regInfo.tempFileName);
RegistryKey result = findKey(root, obj.getKey().getValue().toString());
if (result == null) {
// Take another shot looking for the key minus the first part of the path (sometimes the
// hive file name is here). This should only happen if the hive name started
// with "HKEY"
if ((obj.getHive() != null)
&& obj.getHive().getValue().toString().startsWith("HKEY")) { //NON-NLS
String[] parts = obj.getKey().getValue().toString().split("\\\\");
String newKey = "";
for (int i = 1; i < parts.length; i++) {
if (newKey.length() > 0) {
newKey += "\\";
}
newKey += parts[i];
}
result = findKey(root, newKey);
}
if (result == null) {
return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue(), //NON-NLS
spacing, ObservableResult.ObservableState.FALSE, null);
}
}
if ((obj.getValues() == null) || (obj.getValues().getValues().isEmpty())) {
// No values to test
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
artData.add(new StixArtifactData(a_regInfo.abstractFile.getId(), id, "Registry")); //NON-NLS
return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue(), //NON-NLS
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Test all the values
for (org.mitre.cybox.objects.RegistryValueType stixRegValue : obj.getValues().getValues()) {
try {
for (RegistryValue valFromFile : result.getValueList()) {
// Test if the name field matches (if present)
boolean nameSuccess = true; // True if the name matches or isn't present
if (stixRegValue.getName() != null) {
try {
nameSuccess = compareStringObject(stixRegValue.getName(), valFromFile.getName());
} catch (UnsupportedEncodingException ex) {
nameSuccess = false;
}
}
boolean valueSuccess = true;
if (nameSuccess && (stixRegValue.getData() != null)) {
switch (valFromFile.getValueType()) {
case REG_SZ:
case REG_EXPAND_SZ:
try {
valueSuccess = compareStringObject(stixRegValue.getData(),
valFromFile.getValue().getAsString());
} catch (UnsupportedEncodingException ex) {
valueSuccess = false;
}
break;
case REG_DWORD:
case REG_BIG_ENDIAN:
case REG_QWORD:
// Only support "equals" for now.
if ((stixRegValue.getData().getCondition() == null)
|| (stixRegValue.getData().getCondition() == ConditionTypeEnum.EQUALS)) {
// Try to convert the STIX string to a long
try {
long stixValue = Long.decode(stixRegValue.getData().getValue().toString());
try {
valueSuccess = (stixValue == valFromFile.getValue().getAsNumber());
} catch (UnsupportedEncodingException ex) {
valueSuccess = false;
}
} catch (NumberFormatException ex) {
// We probably weren't looking at a numeric field to begin with,
// so getting this exception isn't really an error.
valueSuccess = false;
}
} else {
valueSuccess = false;
}
break;
default:
// Nothing to do here. These are the types we don't handle:
// REG_BIN, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE,
// REG_RESOURCE_LIST, REG_RESOURCE_REQUIREMENTS_LIST
}
}
if (nameSuccess && valueSuccess) {
// Found a match for all values
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
artData.add(new StixArtifactData(a_regInfo.abstractFile.getId(), id, "Registry")); //NON-NLS
return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue() //NON-NLS
+ " and value " + stixRegValue.getName().getValue().toString() //NON-NLS
+ " = " + stixRegValue.getData().getValue().toString(),
spacing, ObservableResult.ObservableState.TRUE, artData);
}
}
} catch (Exception ex) {
// Broad catch here becase the registry parser can create all kinds of exceptions beyond what it reports.
return new ObservableResult(id, "RegistryObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
} catch (TskCoreException ex) {
return new ObservableResult(id, "RegistryObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
return new ObservableResult(id, "RegistryObject: Not done", //NON-NLS
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
public RegistryKey openRegistry(String hive) throws TskCoreException {
try {
RegistryHiveFile regFile = new RegistryHiveFile(new File(hive));
RegistryKey root = regFile.getRoot();
return root;
} catch (IOException ex) {
throw new TskCoreException("Error opening registry file - " + ex.getLocalizedMessage()); //NON-NLS
} catch (RegistryParseException ex) {
throw new TskCoreException("Error opening root node of registry - " + ex.getLocalizedMessage()); //NON-NLS
}
}
/**
* Go down the registry tree to find a key with the given name.
*
* @param root Root of the registry hive
* @param name Name of the subkey to seach for
*
* @return The matching subkey or null if not found
*/
public RegistryKey findKey(RegistryKey root, String name) {
RegistryKey currentKey = root;
// Split the key name into parts
String[] parts = name.split("\\\\");
for (String part : parts) {
if (part.length() > 0) {
try {
currentKey = currentKey.getSubkey(part);
} catch (Exception ex) {
// We get an exception if the value wasn't found (not a RegistryParseException).
// There doesn't seem to be a cleaner way to test for existance without cycling though
// everything ourselves. (Broad catch because things other than RegistryParseException
// can happen)
return null;
}
}
}
// If we make it this far, we've found it
return currentKey;
}
/**
* Copy all registry hives to the temp directory and return the list of
* created files.
*
* @return Paths to copied registry files.
*/
public static List<RegistryFileInfo> copyRegistryFiles() throws TskCoreException {
// First get all the abstract files
List<AbstractFile> regFilesAbstract = findRegistryFiles();
// List to hold all the extracted file names plus their abstract file
List<RegistryFileInfo> regFilesLocal = new ArrayList<RegistryFileInfo>();
// Make the temp directory
String tmpDir = Case.getCurrentCase().getTempDirectory() + File.separator + "STIX"; //NON-NLS
File dir = new File(tmpDir);
if (dir.exists() == false) {
dir.mkdirs();
}
long index = 1;
for (AbstractFile regFile : regFilesAbstract) {
String regFileName = regFile.getName();
String regFileNameLocal = tmpDir + File.separator + regFileName + "_" + index;
File regFileNameLocalFile = new File(regFileNameLocal);
try {
// Don't save any unallocated versions
if (regFile.getMetaFlagsAsString().contains("Allocated")) { //NON-NLS
ContentUtils.writeToFile(regFile, regFileNameLocalFile);
regFilesLocal.add(new EvalRegistryObj().new RegistryFileInfo(regFile, regFileNameLocal));
}
} catch (IOException ex) {
throw new TskCoreException(ex.getLocalizedMessage());
}
index++;
}
return regFilesLocal;
}
/**
* Search for the registry hives on the system. Mostly copied from
* RecentActivity
*/
private static List<AbstractFile> findRegistryFiles() throws TskCoreException {
List<AbstractFile> registryFiles = new ArrayList<AbstractFile>();
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
for (Content ds : Case.getCurrentCase().getDataSources()) {
// find the user-specific ntuser-dat files
registryFiles.addAll(fileManager.findFiles(ds, "ntuser.dat")); //NON-NLS
// find the system hives
String[] regFileNames = new String[]{"system", "software", "security", "sam"}; //NON-NLS
for (String regFileName : regFileNames) {
List<AbstractFile> allRegistryFiles = fileManager.findFiles(ds, regFileName, "/system32/config"); //NON-NLS
for (AbstractFile regFile : allRegistryFiles) {
// Don't want anything from regback
if (!regFile.getParentPath().contains("RegBack")) { //NON-NLS
registryFiles.add(regFile);
}
}
}
}
return registryFiles;
}
private void setUnsupportedFieldWarnings() {
List<String> fieldNames = new ArrayList<String>();
if (obj.getNumberValues() != null) {
fieldNames.add("Number_Values"); //NON-NLS
}
if (obj.getModifiedTime() != null) {
fieldNames.add("Modified_Time"); //NON-NLS
}
if (obj.getCreatorUsername() != null) {
fieldNames.add("Creator_Username"); //NON-NLS
}
if (obj.getHandleList() != null) {
fieldNames.add("Handle_List"); //NON-NLS
}
if (obj.getNumberSubkeys() != null) {
fieldNames.add("Number_Subkeys"); //NON-NLS
}
if (obj.getSubkeys() != null) {
fieldNames.add("Subkeys"); //NON-NLS
}
if (obj.getByteRuns() != null) {
fieldNames.add("Byte_Runs"); //NON-NLS
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported field(s): " + warningStr); //NON-NLS
}
/**
* Class to keep track of the abstract file and temp file that goes with
* each registry hive.
*/
public class RegistryFileInfo {
private final AbstractFile abstractFile;
private final String tempFileName;
public RegistryFileInfo(AbstractFile a_abstractFile, String a_tempFileName) {
abstractFile = a_abstractFile;
tempFileName = a_tempFileName;
}
}
}