Files
volatility3/volatility/framework/layers/registry.py
T

216 lines
9.5 KiB
Python

import logging
import os.path as os_path
import typing
from volatility.framework import constants, exceptions, interfaces, objects
from volatility.framework.configuration import requirements
from volatility.framework.configuration.requirements import IntRequirement
from volatility.framework.interfaces.configuration import TranslationLayerRequirement
from volatility.framework.symbols import intermed
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,
os: str = "Unknown") -> None:
super().__init__(context, config_path, name, os)
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 = context.symbol_space.free_table_name("registry")
reg_path = "file://" + os_path.join(os_path.dirname(__file__), '..', 'symbols', 'windows', 'reg.json')
table = intermed.IntermediateSymbolTable(context = context, config_path = config_path,
name = self._reg_table_name, isf_url = reg_path)
context.symbol_space.append(table)
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))
self._base_block = self.hive.BaseBlock.dereference()
self._minaddr = 0
self._maxaddr = self._base_block.Length
if self._base_block.Length <= 0:
raise exceptions.StructureException(
"Invalid registry base_block length: {}".format(self._base_block.Length))
@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"""
return self._base_block.RootCell
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) -> interfaces.objects.ObjectInterface:
"""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 not return_list else [self.get_node(self.root_cell_offset)]
if key.endswith("\\"):
key = key[:-1]
key_array = key.split('\\')
found_key = [] # type: typing.List[str]
while key_array and node_key:
subkeys = node_key.get_subkeys() if not return_list else node_key[-1].get_subkeys()
for subkey in subkeys:
if subkey.get_name() == key_array[0]:
node_key = subkey if not return_list else node_key + [subkey]
found_key, key_array = found_key + [key_array[0]], key_array[1:]
break
else:
node_key = None
if not node_key:
raise KeyError("Key {} not found under {}", key_array[0], '\\'.join(found_key))
return node_key
def visit_nodes(self,
visitor: typing.Callable[[objects.Struct], None],
node: typing.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) -> typing.List[interfaces.configuration.RequirementInterface]:
return [IntRequirement(name = 'hive_offset', description = '', default = 0, optional = False),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
TranslationLayerRequirement(name = 'base_layer', 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) -> typing.Iterable[typing.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:
raise RegistryFormatException("Cell address outside expected HBIN")
response.append((offset, translated_offset, length, self._base_layer))
length -= length
return response
@property
def dependencies(self) -> typing.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