Files
volatility3/volatility/framework/symbols/windows/extensions/registry.py
T

281 lines
12 KiB
Python

# This file was contributed to the Volatility Framework Version 3.
# Copyright (C) 2018 Volatility Foundation.
#
# THE LICENSED WORK IS PROVIDED UNDER THE TERMS OF THE Volatility Contributors
# Public License V1.0("LICENSE") AS FIRST COMPLETED BY: Volatility Foundation,
# Inc. ANY USE, PUBLIC DISPLAY, PUBLIC PERFORMANCE, REPRODUCTION OR DISTRIBUTION
# OF, OR PREPARATION OF SUBSEQUENT WORKS, DERIVATIVE WORKS OR DERIVED WORKS BASED
# ON, THE LICENSED WORK CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS LICENSE AND ITS
# TERMS, WHETHER OR NOT SUCH RECIPIENT READS THE TERMS OF THE LICENSE. "LICENSED
# WORK,” “RECIPIENT" AND “DISTRIBUTOR" ARE DEFINED IN THE LICENSE. A COPY OF THE
# LICENSE IS LOCATED IN THE TEXT FILE ENTITLED "LICENSE.txt" ACCOMPANYING THE
# CONTENTS OF THIS FILE. IF A COPY OF THE LICENSE DOES NOT ACCOMPANY THIS FILE, A
# COPY OF THE LICENSE MAY ALSO BE OBTAINED AT THE FOLLOWING WEB SITE:
# https://www.volatilityfoundation.org/license/vcpl_v1.0
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
# specific language governing rights and limitations under the License.
#
import enum
import logging
import struct
from typing import Optional, Iterable, Union
from volatility.framework import constants, exceptions, objects, interfaces
from volatility.framework.layers.registry import RegistryHive
from volatility.framework.symbols import intermed
vollog = logging.getLogger(__name__)
BIG_DATA_MAXLEN = 0x3fd8
class RegValueTypes(enum.Enum):
REG_NONE = 0
REG_SZ = 1
REG_EXPAND_SZ = 2
REG_BINARY = 3
REG_DWORD = 4
REG_DWORD_BIG_ENDIAN = 5
REG_LINK = 6
REG_MULTI_SZ = 7
REG_RESOURCE_LIST = 8
REG_FULL_RESOURCE_DESCRIPTOR = 9
REG_RESOURCE_REQUIREMENTS_LIST = 10
REG_QWORD = 11
REG_UNKNOWN = 99999
# TODO: This _missing_() method can replace the get() method below
# if support for Python 3.6 is added in the future
# @classmethod
# def _missing_(cls, value):
# return cls(RegValueTypes.REG_UNKNOWN)
@classmethod
def get(cls, value):
"""An alternative method for using this enum when the value may be unknown.
This is used to support unknown value requests in Python <3.6."""
try:
return cls(value)
except ValueError:
return cls(RegValueTypes.REG_UNKNOWN)
class RegKeyFlags(enum.IntEnum):
KEY_IS_VOLATILE = 0x01
KEY_HIVE_EXIT = 0x02
KEY_HIVE_ENTRY = 0x04
KEY_NO_DELETE = 0x08
KEY_SYM_LINK = 0x10
KEY_COMP_NAME = 0x20
KEY_PREFEF_HANDLE = 0x40
KEY_VIRT_MIRRORED = 0x80
KEY_VIRT_TARGET = 0x100
KEY_VIRTUAL_STORE = 0x200
class _HMAP_ENTRY(objects.Struct):
def get_block_offset(self) -> int:
try:
return self.PermanentBinAddress ^ (self.PermanentBinAddress & 0xf)
except AttributeError:
return self.BlockAddress
class _CMHIVE(objects.Struct):
def get_name(self) -> Optional[interfaces.objects.ObjectInterface]:
"""Determine a name for the hive. Note that some attributes are
unpredictably blank across different OS versions while others are populated,
so we check all possibilities and take the first one that's not empty"""
for attr in ["FileFullPath", "FileUserName", "HiveRootPath"]:
try:
return getattr(self, attr).get_string()
except (AttributeError, exceptions.InvalidAddressException):
pass
return None
name = property(get_name)
class _CM_KEY_BODY(objects.Struct):
"""This represents an open handle to a registry key and
is not tied to the registry hive file format on disk."""
def _skip_key_hive_entry_path(self, kcb_flags):
"""Win10 14393 introduced an extra path element that it skips
over by checking for Flags that contain KEY_HIVE_ENTRY"""
# _CM_KEY_BODY.Trans introduced in Win10 14393
if hasattr(self, "Trans") and RegKeyFlags.KEY_HIVE_ENTRY & kcb_flags == RegKeyFlags.KEY_HIVE_ENTRY:
return True
return False
def get_full_key_name(self) -> str:
output = []
kcb = self.KeyControlBlock
while kcb.ParentKcb:
if kcb.NameBlock.Name == None:
break
if self._skip_key_hive_entry_path(kcb.Flags):
kcb = kcb.ParentKcb
if not kcb:
break
output.append(
kcb.NameBlock.Name.cast(
"string", encoding = "utf8", max_length = kcb.NameBlock.NameLength, errors = "replace"))
kcb = kcb.ParentKcb
return "\\".join(reversed(output))
class _CM_KEY_NODE(objects.Struct):
"""Extension to allow traversal of registry keys"""
def get_volatile(self) -> bool:
if not isinstance(self._context.layers[self.vol.layer_name], RegistryHive):
raise ValueError("Cannot determine volatility of registry key without an offset in a RegistryHive layer")
return bool(self.vol.offset & 0x80000000)
def get_subkeys(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Returns a list of the key nodes"""
hive = self._context.layers[self.vol.layer_name]
if not isinstance(hive, RegistryHive):
raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer")
for index in range(2):
# Use get_cell because it should *always* be a KeyIndex
subkey_node = hive.get_cell(self.SubKeyLists[index]).u.KeyIndex
yield from self._get_subkeys_recursive(hive, subkey_node)
def _get_subkeys_recursive(self, hive: RegistryHive, node: interfaces.objects.ObjectInterface
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Recursively descend a node returning subkeys"""
# The keylist appears to include 4 bytes of key name after each value
# We can either double the list and only use the even items, or
# We could change the array type to a struct with both parts
signature = node.Signature.cast('string', max_length = 2, encoding = 'latin-1')
listjump = None
if signature == 'ri':
listjump = 1
elif signature == 'lh' or signature == 'lf':
listjump = 2
elif node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"):
yield node
else:
vollog.debug("Unexpected node type encountered when traversing subkeys: {}, signature: {}".format(
node.vol.type_name, signature))
if listjump:
node.List.count = node.Count * listjump
for subnode_offset in node.List[::listjump]:
if (subnode_offset & 0x7fffffff) > hive.maximum_address:
vollog.log(constants.LOGLEVEL_VVV,
"Node found with address outside the valid Hive size: {}".format(subnode_offset))
else:
subnode = hive.get_node(subnode_offset)
yield from self._get_subkeys_recursive(hive, subnode)
def get_values(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Returns a list of the Value nodes for a key"""
hive = self._context.layers[self.vol.layer_name]
if not isinstance(hive, RegistryHive):
raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer")
child_list = hive.get_cell(self.ValueList.List).u.KeyList
child_list.count = self.ValueList.Count
for v in child_list:
if v != 0:
node = hive.get_node(v)
if node.vol.type_name.endswith(constants.BANG + '_CM_KEY_VALUE'):
yield node
def get_name(self) -> interfaces.objects.ObjectInterface:
"""Since this is just a casting convenience, it can be a property"""
return self.Name.cast("string", max_length = self.NameLength, encoding = "latin-1")
def get_key_path(self) -> interfaces.objects.ObjectInterface:
reg = self._context.layers[self.vol.layer_name]
if not isinstance(reg, RegistryHive):
raise TypeError("Key was not instantiated on a RegistryHive layer")
# Using the offset adds a significant delay (since it cannot be cached easily)
# if self.vol.offset == reg.get_node(reg.root_cell_offset).vol.offset:
if self.vol.offset == reg.root_cell_offset + 4:
return self.get_name()
return reg.get_node(self.Parent).get_key_path() + '\\' + self.get_name()
class _CM_KEY_VALUE(objects.Struct):
"""Extensions to extract data from CM_KEY_VALUE nodes"""
def get_name(self) -> interfaces.objects.ObjectInterface:
"""Since this is just a casting convenience, it can be a property"""
self.Name.count = self.NameLength
return self.Name.cast("string", max_length = self.NameLength, encoding = "latin-1")
def decode_data(self) -> Union[str, bytes]:
"""Since this is just a casting convenience, it can be a property"""
# Determine if the data is stored inline
datalen = self.DataLength & 0x7fffffff
data = b""
# Check if the data is stored inline
layer = self._context.layers[self.vol.layer_name]
if not isinstance(layer, RegistryHive):
raise TypeError("Key value was not instantiated on a RegistryHive layer")
if self.DataLength & 0x80000000 and (0 > datalen or datalen > 4):
raise ValueError("Unable to read inline registry value with excessive length: {}".format(datalen))
elif self.DataLength & 0x80000000:
data = layer.read(self.Data.vol.offset, datalen)
elif layer.hive.Version == 5 and datalen > 0x4000:
# We're bigdata
big_data = layer.get_node(self.Data)
# Oddly, we get a list of addresses, at which are addresses, which then point to data blocks
for i in range(big_data.Count):
# The value 4 should actually be unsigned-int.size, but since it's a file format that shouldn't change
# the direct value 4 can be used instead
block_offset = layer.get_cell(big_data.List + (i * 4)).cast("unsigned int")
if isinstance(block_offset, int) and block_offset < layer.maximum_address:
amount = min(BIG_DATA_MAXLEN, datalen)
data += layer.read(offset = layer.get_cell(block_offset).vol.offset, length = amount)
datalen -= amount
else:
# Suspect Data actually points to a Cell,
# but the length at the start could be negative so just adding 4 to jump past it
data = layer.read(self.Data + 4, datalen)
self_type = RegValueTypes.get(self.Type)
if self_type == RegValueTypes.REG_DWORD:
if len(data) != struct.calcsize("<L"):
raise ValueError("Size of data does not match the type of registry value {}".format(self.get_name()))
return struct.unpack("<L", data)[0]
if self_type == RegValueTypes.REG_DWORD_BIG_ENDIAN:
if len(data) != struct.calcsize(">L"):
raise ValueError("Size of data does not match the type of registry value {}".format(self.get_name()))
return struct.unpack(">L", data)[0]
if self_type == RegValueTypes.REG_QWORD:
if len(data) != struct.calcsize("<Q"):
raise ValueError("Size of data does not match the type of registry value {}".format(self.get_name()))
return struct.unpack("<Q", data)[0]
if self_type in [RegValueTypes.REG_SZ, RegValueTypes.REG_EXPAND_SZ, RegValueTypes.REG_LINK]:
# truncate after \x00\x00 to ensure it can
output = str(data, encoding = "utf-16-le", errors = 'replace')
if output.find("\x00") > 0:
output = output[:output.find("\x00")]
return output
if self_type == RegValueTypes.REG_MULTI_SZ:
return str(data, encoding = "utf-16-le").split("\x00")[0]
if self_type == RegValueTypes.REG_BINARY:
return data
if self_type == RegValueTypes.REG_NONE:
return ''
# Fall back if it's something weird
vollog.debug("Unknown registry value type encountered: {}".format(self.Type))
return self.Data.cast("string", max_length = datalen, encoding = "latin-1")