mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-22 14:32:21 +02:00
254 lines
12 KiB
Python
254 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 logging
|
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
|
|
|
from volatility.framework import constants, exceptions, interfaces, objects
|
|
from volatility.framework.configuration import requirements
|
|
from volatility.framework.configuration.requirements import IntRequirement, TranslationLayerRequirement
|
|
from volatility.framework.exceptions import InvalidAddressException
|
|
from volatility.framework.symbols import intermed
|
|
from volatility.plugins.windows import pslist
|
|
|
|
vollog = logging.getLogger(__name__)
|
|
|
|
|
|
class RegistryFormatException(exceptions.LayerException):
|
|
"""Thrown when an error occurs with the underlying Registry file format"""
|
|
|
|
|
|
class RegistryInvalidIndex(exceptions.LayerException):
|
|
"""Thrown when an index that doesn't exist or can't be found occurs"""
|
|
|
|
|
|
class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
|
|
|
def __init__(self,
|
|
context: interfaces.context.ContextInterface,
|
|
config_path: str,
|
|
name: str,
|
|
metadata: Optional[Dict[str, Any]] = None) -> None:
|
|
super().__init__(context = context, config_path = config_path, name = name, metadata = metadata)
|
|
|
|
self._base_layer = self.config["base_layer"]
|
|
self._hive_offset = self.config["hive_offset"]
|
|
self._table_name = self.config["nt_symbols"]
|
|
|
|
self._reg_table_name = intermed.IntermediateSymbolTable.create(context, self._config_path, 'windows',
|
|
'registry')
|
|
|
|
self.hive = self.context.object(self._table_name + constants.BANG + "_CMHIVE", self._base_layer,
|
|
self._hive_offset).Hive
|
|
|
|
# TODO: Check the checksum
|
|
if self.hive.Signature != 0xbee0bee0:
|
|
raise RegistryFormatException("Registry hive at {} does not have a valid signature".format(
|
|
self._hive_offset))
|
|
|
|
# Win10 17063 introduced the Registry process to map most hives. Check
|
|
# if it exists and update RegistryHive._base_layer
|
|
for proc in pslist.PsList.list_processes(self.context, self.config['base_layer'], self.config['nt_symbols']):
|
|
proc_name = proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace')
|
|
if proc_name == "Registry" and proc.InheritedFromUniqueProcessId == 4:
|
|
proc_layer_name = proc.add_process_layer()
|
|
self._base_layer = proc_layer_name
|
|
break
|
|
|
|
self._base_block = self.hive.BaseBlock.dereference()
|
|
|
|
self._minaddr = 0
|
|
# If there's no base_block, we don't know how big the hive layer is
|
|
# We also don't know the root_cell_offset, so we use a hardcoded value of 0x20
|
|
self._maxaddr = self._base_block.Length or 0x7fffffff
|
|
|
|
@property
|
|
def hive_offset(self) -> int:
|
|
return self._hive_offset
|
|
|
|
@property
|
|
def address_mask(self) -> int:
|
|
"""Return a mask that allows for the volatile bit to be set"""
|
|
return super().address_mask | 0x80000000
|
|
|
|
@property
|
|
def root_cell_offset(self) -> int:
|
|
"""Returns the offset for the root cell in this hive"""
|
|
try:
|
|
if self._base_block.Length > 0:
|
|
return self._base_block.RootCell
|
|
except InvalidAddressException:
|
|
pass
|
|
return 0x20
|
|
|
|
def get_cell(self, cell_offset: int) -> 'objects.Struct':
|
|
"""Returns the appropriate Cell value for a cell offset"""
|
|
# This would be an _HCELL containing CELL_DATA, but to save time we skip the size of the HCELL
|
|
cell = self._context.object(
|
|
symbol = self._table_name + constants.BANG + "_CELL_DATA", offset = cell_offset + 4, layer_name = self.name)
|
|
return cell
|
|
|
|
def get_node(self, cell_offset: int) -> 'objects.Struct':
|
|
"""Returns the appropriate Node, interpreted from the Cell based on its Signature"""
|
|
cell = self.get_cell(cell_offset)
|
|
signature = cell.cast('string', max_length = 2, encoding = 'latin-1')
|
|
if signature == 'nk':
|
|
return cell.u.KeyNode
|
|
elif signature == 'sk':
|
|
return cell.u.KeySecurity
|
|
elif signature == 'vk':
|
|
return cell.u.KeyValue
|
|
elif signature == 'db':
|
|
# Big Data
|
|
return cell.u.ValueData
|
|
elif signature == 'lf' or signature == 'lh' or signature == 'ri':
|
|
# Fast Leaf, Hash Leaf, Index Root
|
|
return cell.u.KeyIndex
|
|
else:
|
|
# It doesn't matter that we use KeyNode, we're just after the first two bytes
|
|
vollog.debug("Unknown Signature {} (0x{:x}) at offset {}".format(signature, cell.u.KeyNode.Signature,
|
|
cell_offset))
|
|
return cell
|
|
|
|
def get_key(self, key: str, return_list: bool = False) -> Union[List[objects.Struct], objects.Struct]:
|
|
"""Gets a specific registry key by key path
|
|
|
|
return_list specifies whether the return result will be a single node (default) or a list of nodes from
|
|
root to the current node (if return_list is true).
|
|
"""
|
|
node_key = [self.get_node(self.root_cell_offset)]
|
|
if key.endswith("\\"):
|
|
key = key[:-1]
|
|
key_array = key.split('\\')
|
|
found_key = [] # type: List[str]
|
|
while key_array and node_key:
|
|
subkeys = node_key[-1].get_subkeys()
|
|
for subkey in subkeys:
|
|
# registry keys are not case sensitive so compare lowercase
|
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724946(v=vs.85).aspx
|
|
if subkey.get_name().lower() == key_array[0].lower():
|
|
node_key = node_key + [subkey]
|
|
found_key, key_array = found_key + [key_array[0]], key_array[1:]
|
|
break
|
|
else:
|
|
node_key = []
|
|
if not node_key:
|
|
raise KeyError("Key {} not found under {}", key_array[0], '\\'.join(found_key))
|
|
if return_list:
|
|
return node_key
|
|
return node_key[-1]
|
|
|
|
def visit_nodes(self, visitor: Callable[[objects.Struct], None], node: Optional[objects.Struct] = None) -> None:
|
|
"""Applies a callable (visitor) to all nodes within the registry tree from a given node"""
|
|
if not node:
|
|
node = self.get_node(self.root_cell_offset)
|
|
visitor(node)
|
|
for node in node.get_subkeys():
|
|
self.visit_nodes(visitor, node)
|
|
|
|
@staticmethod
|
|
def _mask(value: int, high_bit: int, low_bit: int) -> int:
|
|
"""Returns the bits of a value between highbit and lowbit inclusive"""
|
|
high_mask = (2 ** (high_bit + 1)) - 1
|
|
low_mask = (2 ** low_bit) - 1
|
|
mask = (high_mask ^ low_mask)
|
|
# print(high_bit, low_bit, bin(mask), bin(value))
|
|
return value & mask
|
|
|
|
@classmethod
|
|
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
|
return [
|
|
IntRequirement(
|
|
name = 'hive_offset',
|
|
description = 'Offset within the base layer at which the hive lives',
|
|
default = 0,
|
|
optional = False),
|
|
requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"),
|
|
TranslationLayerRequirement(
|
|
name = 'base_layer', description = 'Layer in which the registry hive lives', optional = False)
|
|
]
|
|
|
|
def _translate(self, offset: int) -> int:
|
|
"""Translates a single cell index to a cell memory offset and the suboffset within it"""
|
|
|
|
# Ignore the volatile bit when determining maxaddr validity
|
|
if offset & 0x7fffffff > self._maxaddr:
|
|
raise RegistryInvalidIndex("Mapping request for value greater than maxaddr")
|
|
|
|
volatile = self._mask(offset, 31, 31) >> 31
|
|
storage = self.hive.Storage[volatile]
|
|
dir_index = self._mask(offset, 30, 21) >> 21
|
|
table_index = self._mask(offset, 20, 12) >> 12
|
|
suboffset = self._mask(offset, 11, 0) >> 0
|
|
|
|
table = storage.Map.Directory[dir_index]
|
|
entry = table.Table[table_index]
|
|
return entry.get_block_offset() + suboffset
|
|
|
|
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
|
|
|
|
# TODO: Check the offset and offset + length are not outside the norms
|
|
if length < 0:
|
|
raise ValueError("Mapping length of RegistryHive must be positive or zero")
|
|
|
|
response = []
|
|
while length > 0:
|
|
# Try using the symbol first
|
|
hbin_offset = self._translate(self._mask(offset, 31, 12))
|
|
hbin = self.context.object(
|
|
self._reg_table_name + constants.BANG + "_HBIN", offset = hbin_offset, layer_name = self._base_layer)
|
|
|
|
# Now get the cell's offset and figure out if it goes outside the bin
|
|
# We could use some invariants such as whether cells always fit within a bin?
|
|
translated_offset = self._translate(offset)
|
|
if translated_offset + length > hbin_offset + hbin.Size:
|
|
# Generally suggests the hbin is a large (larger than a page) bin
|
|
# In which case, hunt backwards for the right hbin header and check the size again
|
|
while hbin.Signature.cast("string", max_length = 4, encoding = "latin-1") != 'hbin':
|
|
hbin_offset = hbin_offset - 0x1000
|
|
hbin = self.context.object(
|
|
self._reg_table_name + constants.BANG + "_HBIN",
|
|
offset = hbin_offset,
|
|
layer_name = self._base_layer)
|
|
if translated_offset + length > hbin_offset + hbin.Size and hbin.Size > 0:
|
|
raise RegistryFormatException("Cell address {} outside expected HBIN limit: {}".format(
|
|
hex(translated_offset + length), hex(hbin_offset + hbin.Size)))
|
|
response.append((offset, translated_offset, length, self._base_layer))
|
|
length -= length
|
|
return response
|
|
|
|
@property
|
|
def dependencies(self) -> List[str]:
|
|
"""Returns a list of layer names that this layer translates onto"""
|
|
return [self.config['base_layer']]
|
|
|
|
def is_valid(self, offset: int, length: int = 1) -> bool:
|
|
"""Returns a boolean based on whether the offset is valid or not"""
|
|
# TODO: Fix me
|
|
return True
|
|
|
|
@property
|
|
def minimum_address(self) -> int:
|
|
return self._minaddr
|
|
|
|
@property
|
|
def maximum_address(self) -> int:
|
|
return self._maxaddr
|